Files
wehub-resource-sync 542cfa195c
CI / Frontend build (push) Failing after 9m6s
CI / Plugin validate (push) Failing after 9m27s
CI / Python lint (push) Failing after 16m1s
CI / Tests (push) Successful in 18m0s
Deploy / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:27 +08:00

139 lines
6.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PixelRAG Search</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, system-ui, sans-serif; background: #0a0a0a; color: #e0e0e0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; }
.container { max-width: 900px; width: 100%; padding: 2rem 1.5rem; }
h1 { font-size: 2rem; font-weight: 300; margin-bottom: 0.3rem; color: #fff; }
h1 span { font-weight: 600; }
.subtitle { color: #888; margin-bottom: 2rem; font-size: 0.9rem; }
.search-box { display: flex; gap: 0.5rem; margin-bottom: 0.8rem; }
input[type="text"] { flex: 1; padding: 0.75rem 1rem; border: 1px solid #333; border-radius: 8px; background: #1a1a1a; color: #fff; font-size: 1rem; outline: none; transition: border-color 0.2s; }
input[type="text"]:focus { border-color: #4a9eff; }
button { padding: 0.75rem 1.5rem; border: none; border-radius: 8px; background: #4a9eff; color: #fff; font-size: 1rem; cursor: pointer; transition: background 0.2s; white-space: nowrap; }
button:hover { background: #3a8eef; }
button:disabled { background: #333; cursor: not-allowed; }
.controls { display: flex; gap: 1rem; align-items: center; margin-bottom: 2rem; font-size: 0.85rem; color: #888; }
.controls label { display: flex; align-items: center; gap: 0.3rem; }
.controls select, .controls input[type="number"] { background: #1a1a1a; border: 1px solid #333; border-radius: 4px; color: #e0e0e0; padding: 0.3rem 0.5rem; font-size: 0.85rem; }
.controls input[type="number"] { width: 60px; }
.status { font-size: 0.85rem; color: #888; margin-bottom: 1.5rem; }
.status .time { color: #4a9eff; }
.results { display: flex; flex-direction: column; gap: 1rem; }
.hit { display: flex; gap: 1rem; padding: 1rem; border: 1px solid #222; border-radius: 10px; background: #111; transition: border-color 0.2s; }
.hit:hover { border-color: #333; }
.hit-rank { font-size: 1.5rem; font-weight: 700; color: #333; min-width: 2rem; display: flex; align-items: flex-start; justify-content: center; padding-top: 0.2rem; }
.hit-content { flex: 1; min-width: 0; }
.hit-title { font-size: 1rem; font-weight: 500; margin-bottom: 0.3rem; }
.hit-title a { color: #4a9eff; text-decoration: none; }
.hit-title a:hover { text-decoration: underline; }
.hit-meta { font-size: 0.8rem; color: #666; display: flex; gap: 1rem; flex-wrap: wrap; }
.hit-meta .score { color: #4a9eff; font-weight: 600; }
.hit-meta code { background: #1a1a1a; padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.75rem; }
.empty { text-align: center; padding: 3rem; color: #555; }
.error { color: #ff6b6b; padding: 1rem; border: 1px solid #ff6b6b33; border-radius: 8px; background: #ff6b6b0a; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
.loading { animation: pulse 1s infinite; }
</style>
</head>
<body>
<div class="container">
<h1><span>PixelRAG</span> Search</h1>
<p class="subtitle">Visual retrieval over 15.7M Wikipedia screenshot tiles</p>
<div class="search-box">
<input type="text" id="query" placeholder="Search Wikipedia visually..." autofocus>
<button id="searchBtn" onclick="search()">Search</button>
</div>
<div class="controls">
<label>Results: <input type="number" id="nDocs" value="10" min="1" max="100"></label>
<label>Endpoint:
<select id="endpoint">
<option value="http://localhost:30001">:30001 text (15.7M)</option>
<option value="http://localhost:30002">:30002 pixel base (28M)</option>
<option value="http://localhost:30003">:30003 pixel LoRA (28M)</option>
</select>
</label>
</div>
<div id="status" class="status"></div>
<div id="results" class="results"></div>
</div>
<script>
const queryInput = document.getElementById('query');
const resultsDiv = document.getElementById('results');
const statusDiv = document.getElementById('status');
const searchBtn = document.getElementById('searchBtn');
queryInput.addEventListener('keydown', e => { if (e.key === 'Enter') search(); });
async function search() {
const query = queryInput.value.trim();
if (!query) return;
const endpoint = document.getElementById('endpoint').value;
const nDocs = parseInt(document.getElementById('nDocs').value) || 10;
searchBtn.disabled = true;
searchBtn.textContent = 'Searching...';
statusDiv.innerHTML = '<span class="loading">Embedding query and searching index...</span>';
resultsDiv.innerHTML = '';
const t0 = performance.now();
try {
const resp = await fetch(`${endpoint}/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ queries: [{ text: query }], n_docs: nDocs }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
const data = await resp.json();
const elapsed = ((performance.now() - t0) / 1000).toFixed(2);
const hits = data.results?.[0]?.hits || [];
statusDiv.innerHTML = `${hits.length} results in <span class="time">${elapsed}s</span>`;
if (hits.length === 0) {
resultsDiv.innerHTML = '<div class="empty">No results found</div>';
return;
}
resultsDiv.innerHTML = hits.map((hit, i) => {
const slug = hit.url ? hit.url.split('/wiki/').pop() : '?';
const title = decodeURIComponent(slug).replace(/_/g, ' ');
return `
<div class="hit">
<div class="hit-rank">${i + 1}</div>
<div class="hit-content">
<div class="hit-title"><a href="${hit.url}" target="_blank">${title}</a></div>
<div class="hit-meta">
<span class="score">${hit.score.toFixed(3)}</span>
<span>article #${hit.article_id}</span>
<span>tile ${hit.tile_index}:${hit.chunk_index}</span>
<code>${hit.tile_height}px @ y=${hit.y_offset}</code>
</div>
</div>
</div>`;
}).join('');
} catch (err) {
statusDiv.innerHTML = '';
resultsDiv.innerHTML = `<div class="error">${err.message}</div>`;
} finally {
searchBtn.disabled = false;
searchBtn.textContent = 'Search';
}
}
</script>
</body>
</html>