import React, { useState } from 'react'; if (!query.trim()) return; setLoading(true); setResults([]); setAnswer(''); try { const res = await fetch('/api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }), }); if (!res.ok) throw new Error(await res.text()); const payload = await res.json(); setResults(payload.hits || []); setAnswer(payload.answer || ''); } catch (err) { console.error(err); setAnswer('Error: ' + String(err)); } finally { setLoading(false); } } from fastapi import FastAPI, HTTPException id: str text: str source: Optional[str] = None class IndexPayload(BaseModel): docs: List[Document] class QueryPayload(BaseModel): query: str top_k: Optional[int] = 5 app = FastAPI() # Global store VECTOR_DIM = 64 DOCS: List[Document] = [] VECTORS = None @app.post('/api/index') async def index_docs(payload: IndexPayload): global DOCS, VECTORS docs = payload.docs texts = [d.text for d in docs] embeddings = embed_texts(texts) # append start = len(DOCS) DOCS.extend(docs) if VECTORS is None: VECTORS = embeddings else: VECTORS = np.vstack([VECTORS, embeddings]) # If faiss available, you would build/save the index here return {"indexed": len(docs), "total": len(DOCS)} @app.post('/api/search') async def search(q: QueryPayload): global DOCS, VECTORS if not DOCS: return {"hits": [], "answer": "No documents indexed."} q_emb = embed_texts([q.query])[0:1] # shape (1, D) # cosine similarity via dot on normalized vectors if VECTORS is None: return {"hits": [], "answer": "No vectors available."} scores = (VECTORS @ q_emb.T).squeeze() # dot product topk_idx = np.argsort(-scores)[: q.top_k] hits = [] for idx in topk_idx: hits.append({ "id": DOCS[idx].id, "text": DOCS[idx].text, "source": DOCS[idx].source, "score": float(scores[idx]), }) # Simple answer synthesis: join top passages and optionally call an LLM combined = "\n\n".join([h['text'] for h in hits]) # In production: call your LLM with prompt: question + passages -> final answer answer = f"Synthesis (local): Found {len(hits)} relevant passages.\n\n{combined[:2000]}" return {"hits": hits, "answer": answer} @app.get('/api/health') def health(): return {"status": "ok"} return (

AI Search Engine — Starter

setQuery(e.target.value)} placeholder="Ask something..." className="flex-1 p-2 border rounded" />

Answer

{answer || 'No answer yet.'}

Top hits

    {results.length === 0 &&
  • No hits yet.
  • } {results.map((r, i) => (
  • {r.text}
    {r.source &&
    Source: {r.source}
    } {r.score !== undefined && (
    Score: {r.score.toFixed(3)}
    )}
  • ))}
); }