// ── Config ────────────────────────────────────────────────────── const API_BASE = ""; // "" = same origin (HF Spaces). "http://localhost:8000" for local dev. // ── State ─────────────────────────────────────────────────────── let sessions = []; // [{id, title, messages:[]}] let activeSessionId = null; let isLoading = false; let sidebarCollapsed = localStorage.getItem("sidebarCollapsed") === "true"; // ── Init ──────────────────────────────────────────────────────── const textarea = document.getElementById("query-input"); const sendBtn = document.getElementById("send-btn"); const msgsList = document.getElementById("messages-list"); // Apply sidebar collapsed state on load if (sidebarCollapsed) { document.getElementById("sidebar").classList.add("collapsed"); } textarea.addEventListener("input", () => { textarea.style.height = "auto"; textarea.style.height = Math.min(textarea.scrollHeight, 140) + "px"; }); textarea.addEventListener("keydown", e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); } }); // ── Screen switching ───────────────────────────────────────────── function showScreen(name) { document.querySelectorAll(".screen").forEach(s => s.classList.remove("active")); document.getElementById("screen-" + name).classList.add("active"); } // ── Sidebar toggle ─────────────────────────────────────────────── function toggleSidebar() { const sidebar = document.getElementById("sidebar"); sidebarCollapsed = !sidebarCollapsed; sidebar.classList.toggle("collapsed"); localStorage.setItem("sidebarCollapsed", sidebarCollapsed); } // ── Session management ─────────────────────────────────────────── function createSession(firstQuery) { const id = Date.now(); const title = firstQuery.length > 45 ? firstQuery.substring(0, 45) + "…" : firstQuery; const session = { id, title, messages: [] }; sessions.unshift(session); activeSessionId = id; renderSessionsList(); return session; } function getActiveSession() { return sessions.find(s => s.id === activeSessionId); } function switchSession(id) { activeSessionId = id; renderSessionsList(); renderMessages(); showScreen("chat"); const session = getActiveSession(); document.getElementById("topbar-title").textContent = session.title; } function newChat() { activeSessionId = null; msgsList.innerHTML = ""; showScreen("welcome"); document.getElementById("topbar-title").textContent = "New Research Session"; renderSessionsList(); textarea.focus(); } function renderSessionsList() { const list = document.getElementById("sessions-list"); if (sessions.length === 0) { list.innerHTML = '
No sessions yet
'; return; } list.innerHTML = sessions.map(s => `
${escHtml(s.title)} ${s.messages.filter(m => m.role === "ai").length}
`).join(""); } function renderMessages() { const session = getActiveSession(); if (!session) return; msgsList.innerHTML = ""; session.messages.forEach(msg => { if (msg.role === "user") appendUserBubble(msg.text, false); else if (msg.role === "ai") appendAIBubble(msg.data, false); else if (msg.role === "error") appendErrorBubble(msg.text, false); }); scrollBottom(); } // ── Submit ─────────────────────────────────────────────────────── async function submitQuery() { const query = textarea.value.trim(); if (!query || isLoading) return; if (query.length < 10) { showToast("Query too short — minimum 10 characters."); return; } if (query.length > 1000) { showToast("Query too long — maximum 1000 characters."); return; } if (!activeSessionId) { createSession(query); showScreen("chat"); document.getElementById("topbar-title").textContent = getActiveSession().title; } getActiveSession().messages.push({ role: "user", text: query }); textarea.value = ""; textarea.style.height = "auto"; appendUserBubble(query); const loaderId = appendLoader(); setLoading(true); try { const res = await fetch(`${API_BASE}/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, session_id: activeSessionId ? String(activeSessionId) : "default" }) }); const data = await res.json(); removeLoader(loaderId); if (!res.ok) { const msg = data.detail || "Something went wrong. Please try again."; getActiveSession().messages.push({ role: "error", text: msg }); appendErrorBubble(msg); } else { getActiveSession().messages.push({ role: "ai", data }); appendAIBubble(data); } } catch (err) { removeLoader(loaderId); const msg = "Could not reach the server. The Space may be waking up — try again in 30 seconds."; getActiveSession().messages.push({ role: "error", text: msg }); appendErrorBubble(msg); } setLoading(false); scrollBottom(); } function usesuggestion(el) { textarea.value = el.textContent; textarea.dispatchEvent(new Event("input")); submitQuery(); } // ── Bubble renderers ───────────────────────────────────────────── function appendUserBubble(text, scroll = true) { const div = document.createElement("div"); div.className = "msg msg-user"; div.innerHTML = `
${escHtml(text)}
`; msgsList.appendChild(div); if (scroll) scrollBottom(); } function appendAIBubble(data, scroll = true) { const verified = data.verification_status === true || data.verification_status === "verified"; const badgeClass = verified ? "verified" : "unverified"; const badgeText = verified ? "✓ Verified" : "⚠ Unverified"; const truncNote = data.truncated ? `
3 of 5 retrieved documents used — context limit reached.
` : ""; const sourceCount = (data.sources || []).length; const sourcesBtn = sourceCount > 0 ? `` : ""; const latency = data.latency_ms ? `${Math.round(data.latency_ms)}ms` : ""; const div = document.createElement("div"); div.className = "msg msg-ai"; div.innerHTML = `
${formatAnswer(data.answer)}
${truncNote}
${badgeText} ${sourcesBtn} ${latency}
`; msgsList.appendChild(div); if (scroll) scrollBottom(); } function appendErrorBubble(text, scroll = true) { const div = document.createElement("div"); div.className = "msg msg-ai"; div.innerHTML = `
⚠ ${escHtml(text)}
`; msgsList.appendChild(div); if (scroll) scrollBottom(); } function appendLoader() { const id = "loader-" + Date.now(); const div = document.createElement("div"); div.id = id; div.className = "msg msg-ai"; div.innerHTML = `
Searching judgments…
`; msgsList.appendChild(div); scrollBottom(); return id; } function removeLoader(id) { const el = document.getElementById(id); if (el) el.remove(); } // ── Sources panel ──────────────────────────────────────────────── function openSources(sources) { const panel = document.getElementById("sources-panel"); const overlay = document.getElementById("sources-overlay"); const body = document.getElementById("sources-panel-body"); body.innerHTML = sources.map((s, i) => { const meta = s.meta || {}; const id = meta.judgment_id || "Unknown"; const year = meta.year ? ` · ${meta.year}` : ""; const excerpt = (s.text || "").trim().substring(0, 400); return `
${i + 1}
${escHtml(id)}
Supreme Court of India${year}
${escHtml(excerpt)}${s.text && s.text.length > 400 ? "…" : ""}
`; }).join(""); panel.classList.add("open"); overlay.classList.add("open"); requestAnimationFrame(() => { panel.style.transform = "translateX(0)"; }); } function closeSourcesPanel() { const panel = document.getElementById("sources-panel"); const overlay = document.getElementById("sources-overlay"); panel.classList.remove("open"); overlay.classList.remove("open"); } // ── Helpers ────────────────────────────────────────────────────── function setLoading(state) { isLoading = state; sendBtn.disabled = state; const pill = document.getElementById("status-pill"); const text = document.getElementById("status-text"); if (state) { pill.classList.add("loading"); text.textContent = "Searching…"; } else { pill.classList.remove("loading"); text.textContent = "Ready"; } } function scrollBottom() { const c = document.querySelector(".messages-container"); if (c) c.scrollTop = c.scrollHeight; } function escHtml(str) { return String(str || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function escAttr(str) { return String(str || "").replace(/'/g, "'").replace(/"/g, """); } // ── Answer formatter ───────────────────────────────────────────── function formatAnswer(text) { if (!text) return ""; text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); const lines = text.split('\n'); let html = ''; let inTable = false; let tableHtml = ''; let inList = false; let listType = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Table row if (line.trim().startsWith('|')) { if (line.match(/^\|[\s\-|]+\|$/)) continue; // skip separator rows if (!inTable) { tableHtml = ''; inTable = true; } const cells = line.split('|').filter((c, idx, a) => idx > 0 && idx < a.length - 1); tableHtml += '' + cells.map(c => ``).join('') + ''; continue; } else if (inTable) { html += tableHtml + '
${inline(c.trim())}
'; tableHtml = ''; inTable = false; } // Numbered list if (line.match(/^\d+\.\s+/)) { if (!inList || listType !== 'ol') { if (inList) html += ``; html += '
    '; inList = true; listType = 'ol'; } html += `
  1. ${inline(line.replace(/^\d+\.\s+/, ''))}
  2. `; continue; } // Bullet list if (line.match(/^[\*\-]\s+/)) { if (!inList || listType !== 'ul') { if (inList) html += ``; html += '