<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TinySearch WASM Demo</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
background-color: #f8f9fa;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #2c3e50;
margin-bottom: 20px;
}
.search-box {
margin: 20px 0;
}
input[type="text"] {
width: 300px;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 20px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-left: 10px;
}
button:hover {
background: #2980b9;
}
.results {
margin-top: 20px;
}
.result-item {
padding: 15px;
border: 1px solid #e1e8ed;
border-radius: 4px;
margin-bottom: 10px;
background: #f8f9fa;
}
.result-title {
font-weight: bold;
color: #3498db;
text-decoration: none;
font-size: 16px;
}
.result-title:hover {
text-decoration: underline;
}
.no-results {
color: #7f8c8d;
font-style: italic;
}
.status {
color: #7f8c8d;
font-size: 14px;
margin-top: 10px;
}
.error {
color: #e74c3c;
background: #fdf2f2;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>TinySearch WASM Demo</h1>
<p>This demo shows TinySearch running in WebAssembly. Try searching for terms like "rust", "javascript", or "blog".</p>
<div class="search-box">
<input type="text" id="searchInput" placeholder="Enter your search query..." />
<button onclick="performSearch()">Search</button>
</div>
<div id="status" class="status">Loading search engine...</div>
<div id="results" class="results"></div>
</div>
<script>
let wasmModule = null;
let memory = null;
let searchFunction = null;
let freeFunction = null;
async function initializeSearch() {
try {
const response = await fetch('./{WASM_NAME}.wasm');
if (!response.ok) {
throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
}
const wasmBytes = await response.arrayBuffer();
const module = await WebAssembly.instantiate(wasmBytes);
wasmModule = module.instance;
memory = wasmModule.exports.memory;
searchFunction = wasmModule.exports.search;
freeFunction = wasmModule.exports.free_search_result;
if (!searchFunction || !freeFunction || !memory) {
throw new Error('Required WASM exports not found');
}
document.getElementById('status').textContent = 'Search engine ready! Try entering a query above.';
document.getElementById('searchInput').disabled = false;
document.getElementById('searchInput').focus();
} catch (error) {
console.error('Failed to initialize search:', error);
document.getElementById('status').innerHTML = `<div class="error">Failed to initialize search: ${error.message}</div>`;
}
}
function stringToWasmPtr(str) {
const bytes = new TextEncoder().encode(str + '\0');
const ptr = wasmModule.exports.__wbindgen_malloc(bytes.length);
if (!ptr) {
const memoryArray = new Uint8Array(memory.buffer);
const startOffset = 1024; memoryArray.set(bytes, startOffset);
return startOffset;
}
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
return ptr;
}
function wasmPtrToString(ptr) {
if (ptr === 0) return null;
const memoryArray = new Uint8Array(memory.buffer);
let length = 0;
while (memoryArray[ptr + length] !== 0) {
length++;
if (length > 1000000) break; }
const bytes = memoryArray.slice(ptr, ptr + length);
return new TextDecoder().decode(bytes);
}
function performSearch() {
const query = document.getElementById('searchInput').value.trim();
const resultsDiv = document.getElementById('results');
const statusDiv = document.getElementById('status');
if (!wasmModule) {
statusDiv.innerHTML = '<div class="error">Search engine not initialized</div>';
return;
}
if (!query) {
resultsDiv.innerHTML = '';
statusDiv.textContent = 'Enter a search query to see results.';
return;
}
try {
const startTime = performance.now();
let queryPtr;
try {
queryPtr = stringToWasmPtr(query);
} catch (e) {
const queryBytes = new TextEncoder().encode(query + '\0');
const memoryArray = new Uint8Array(memory.buffer);
queryPtr = 1024; memoryArray.set(queryBytes, queryPtr);
}
const resultPtr = searchFunction(queryPtr, 10);
if (wasmModule.exports.__wbindgen_free && queryPtr > 1024) {
wasmModule.exports.__wbindgen_free(queryPtr, query.length + 1);
}
const endTime = performance.now();
const searchTime = (endTime - startTime).toFixed(3);
if (resultPtr === 0) {
resultsDiv.innerHTML = '<div class="no-results">No results found</div>';
statusDiv.textContent = `No results found for "${query}" (${searchTime}ms)`;
return;
}
const resultString = wasmPtrToString(resultPtr);
freeFunction(resultPtr);
if (!resultString) {
resultsDiv.innerHTML = '<div class="no-results">No results found</div>';
statusDiv.textContent = `No results found for "${query}" (${searchTime}ms)`;
return;
}
const results = JSON.parse(resultString);
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="no-results">No results found</div>';
statusDiv.textContent = `No results found for "${query}" (${searchTime}ms)`;
} else {
const resultHTML = results.map(result => `
<div class="result-item">
<a href="${result.url}" class="result-title" target="_blank">${result.title}</a>
</div>
`).join('');
resultsDiv.innerHTML = resultHTML;
statusDiv.textContent = `Found ${results.length} result${results.length !== 1 ? 's' : ''} for "${query}" (${searchTime}ms)`;
}
} catch (error) {
console.error('Search error:', error);
resultsDiv.innerHTML = '<div class="error">Search failed. Check console for details.</div>';
statusDiv.innerHTML = `<div class="error">Search failed: ${error.message}</div>`;
}
}
document.getElementById('searchInput').addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
performSearch();
}
});
document.getElementById('searchInput').disabled = true;
initializeSearch();
</script>
</body>
</html>