{doc.filename}
relevance {Number(doc.relevance_score).toFixed(3)} - {doc.num_matching_chunks} matching chunks
{chunk.text}
import React, { useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import { api } from "./api";
import "./styles.css";
function getSessionId() {
const existing = localStorage.getItem("kyr_session_id");
if (existing) return existing;
const created = `session:${crypto.randomUUID()}`;
localStorage.setItem("kyr_session_id", created);
return created;
}
function formatTtl(seconds) {
if (!seconds) return "temporary";
const minutes = Math.round(seconds / 60);
return `${minutes} min`;
}
function shortSessionId(sessionId) {
return sessionId?.replace("session:", "").slice(0, 8) || "unknown";
}
function clearStoredAuth() {
localStorage.removeItem("kyr_access_token");
localStorage.removeItem("kyr_refresh_token");
localStorage.removeItem("kyr_user");
}
function groundednessLabel(status) {
if (status === "grounded") return "Grounded";
if (status === "partially_grounded") return "Partially grounded";
return "Insufficient evidence";
}
function App() {
const [sessionId, setSessionId] = useState(getSessionId);
const [config, setConfig] = useState(null);
const [status, setStatus] = useState(null);
const [user, setUser] = useState(() => {
const raw = localStorage.getItem("kyr_user");
return raw ? JSON.parse(raw) : null;
});
const [authMode, setAuthMode] = useState("sign-in");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [sourceUrl, setSourceUrl] = useState("");
const [files, setFiles] = useState([]);
const [query, setQuery] = useState("");
const [topK, setTopK] = useState(5);
const [threshold, setThreshold] = useState(0.3);
const [documents, setDocuments] = useState([]);
const [chunks, setChunks] = useState([]);
const [chatQuestion, setChatQuestion] = useState("");
const [answer, setAnswer] = useState("");
const [message, setMessage] = useState("");
const [busy, setBusy] = useState("");
const [activeJob, setActiveJob] = useState(null);
const signedIn = Boolean(user);
useEffect(() => {
bootstrapSession();
}, []);
async function bootstrapSession() {
const token = localStorage.getItem("kyr_access_token");
if (token) {
try {
const current = await api.me();
const currentUser = current.user || current;
localStorage.setItem("kyr_user", JSON.stringify(currentUser));
setUser(currentUser);
} catch {
clearStoredAuth();
setUser(null);
setMessage("Your saved sign-in expired. Please sign in again.");
}
}
await refreshBasics();
}
async function refreshBasics() {
const [configData, statusData] = await Promise.all([api.config(), api.status(sessionId).catch(() => null)]);
setConfig(configData);
setStatus(statusData);
}
async function runAction(label, action) {
setBusy(label);
setMessage("");
try {
await action();
await refreshBasics();
} catch (error) {
setMessage(error.message);
} finally {
setBusy("");
}
}
async function waitForJob(jobId) {
let latest = null;
for (;;) {
latest = await api.job(jobId, sessionId);
setActiveJob(latest);
if (latest.status === "completed") {
setMessage(
`${latest.message}. ${latest.chunks_indexed} chunks indexed. Vectors expire in ${formatTtl(config?.session_ttl_seconds)}.`,
);
return latest;
}
if (latest.status === "failed") {
throw new Error(latest.error || latest.message || "Ingestion failed.");
}
await new Promise((resolve) => setTimeout(resolve, 1500));
}
}
async function handleAuth(event) {
event.preventDefault();
await runAction(authMode === "sign-in" ? "Signing in" : "Creating account", async () => {
const redirectTo = window.location.origin;
const result =
authMode === "sign-in" ? await api.signIn(email, password) : await api.signUp(email, password, redirectTo);
const session = result.session || result;
if (session?.access_token) {
localStorage.setItem("kyr_access_token", session.access_token);
localStorage.setItem("kyr_refresh_token", session.refresh_token || "");
localStorage.setItem("kyr_user", JSON.stringify(session.user));
setUser(session.user);
setMessage("Signed in.");
} else if (result.confirmation_required) {
setMessage("Account created. Check your email if Supabase requires confirmation, then sign in.");
}
});
}
async function handleSignOut() {
await runAction("Signing out", async () => {
await api.signOut().catch(() => null);
clearStoredAuth();
setUser(null);
setDocuments([]);
setChunks([]);
setAnswer("");
});
}
function handleNewSession() {
const created = `session:${crypto.randomUUID()}`;
localStorage.setItem("kyr_session_id", created);
setSessionId(created);
setDocuments([]);
setChunks([]);
setAnswer("");
setActiveJob(null);
api.status(created).then(setStatus).catch(() => null);
setMessage("Started a fresh anonymous workspace. Re-index your source before searching.");
}
async function handleIngestSource(event) {
event.preventDefault();
if (!sourceUrl.trim()) {
setMessage("Paste a public source link first.");
return;
}
await runAction("Indexing source", async () => {
const result = await api.ingestSource(sourceUrl.trim(), sessionId);
setMessage("Ingestion job queued. You can keep using the page while it runs.");
await waitForJob(result.job_id);
});
}
async function handleUpload(event) {
event.preventDefault();
if (!files.length) {
setMessage("Choose at least one file first.");
return;
}
await runAction("Indexing uploads", async () => {
const result = await api.uploadFiles(files, sessionId);
setMessage("Upload ingestion job queued. You can keep using the page while it runs.");
setFiles([]);
await waitForJob(result.job_id);
});
}
async function handleSearch(event) {
event.preventDefault();
if (!query.trim()) {
setMessage("Enter a search question first.");
return;
}
await runAction("Searching", async () => {
const result = await api.search({
query: query.trim(),
sessionId,
topK,
similarityThreshold: threshold,
});
setDocuments(result.documents || []);
setChunks(result.chunks || []);
setAnswer("");
setMessage(result.count ? `Found ${result.count} relevant documents.` : "No matching documents found.");
});
}
async function handleChat(event) {
event.preventDefault();
if (!chatQuestion.trim()) {
setMessage("Ask a follow-up question first.");
return;
}
await runAction("Asking model", async () => {
const result = await api.chat(chatQuestion.trim(), chunks);
setAnswer(result);
});
}
const metrics = useMemo(
() => [
["Indexed chunks", status?.total_chunks ?? "..."],
["Vector DB", config?.vector_backend ?? "..."],
["Embeddings", config?.embedding_provider ?? "..."],
["Chat", config?.chat_provider ?? "..."],
],
[config, status],
);
return (
Index a source, search its chunks, and ask grounded follow-up questions.
Search scope: {shortSessionId(sessionId)}
Anonymous GitHub repositories up to {config?.anonymous_repo_limit_mb ?? 100} MB do not require sign-in.Repository Intelligence
Build the temporary index
Find relevant files
Ask retrieved context
{answer && Retrieved files
{payload?.answer || "No answer returned."}
{citations.length > 0 && ({citation.quote}
}{job.message}
relevance {Number(doc.relevance_score).toFixed(3)} - {doc.num_matching_chunks} matching chunks
{chunk.text}