const API = ''; // same origin const CLIENT_EXAMPLES = { novamart: ['What happens when a product runs out of stock?', 'How do I add a new supplier?', 'How do I update pricing for an item?'], shelfwise: ['What triggers an out-of-stock alert?', 'How does planogram compliance work?', 'How do I configure a new store?'], clinixone: ['What is prior authorization?', 'How are adverse events reported?', 'What are contraindicated drug combinations?'], pharmalink: ['What is formulary pre-approval?', 'How does benefit tier affect drug coverage?', 'What is a pharmacovigilance alert?'], }; let state = { domain: null, client: null, domains: {}, loading: false, }; // ── Boot ────────────────────────────────────────────────────────────────── async function boot() { const res = await fetch(`${API}/config`); const data = await res.json(); state.domains = data.domains; const firstDomain = Object.keys(data.domains)[0]; renderDomainSwitcher(); selectDomain(firstDomain); document.getElementById('send-btn').addEventListener('click', handleSend); document.getElementById('query-input').addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) handleSend(); }); } // ── Switchers ───────────────────────────────────────────────────────────── function renderDomainSwitcher() { const el = document.getElementById('domain-switcher'); el.innerHTML = Object.keys(state.domains).map(d => ` `).join(''); } function selectDomain(domain) { state.domain = domain; document.querySelectorAll('#domain-switcher button').forEach(b => { b.classList.toggle('active', b.dataset.domain === domain); }); const clients = state.domains[domain]; const el = document.getElementById('client-switcher'); el.innerHTML = clients.map(c => ` `).join(''); selectClient(clients[0].id); } function selectClient(clientId) { state.client = clientId; document.querySelectorAll('#client-switcher button').forEach(b => { b.classList.toggle('active', b.dataset.client === clientId); }); showWelcome(clientId); } function showWelcome(clientId) { const messages = getMessages(); messages.innerHTML = ''; document.getElementById('eval-body').innerHTML = `
📋
Ask a question to see evaluation results
`; const examples = CLIENT_EXAMPLES[clientId] || []; const chips = examples.map(q => ` `).join(''); const el = document.createElement('div'); el.className = 'message bot'; el.innerHTML = `
I'm the ${clientId} assistant. Ask me anything about this client's domain — or try one of these:
${chips}
System
`; messages.appendChild(el); } function sendExample(btn) { const input = document.getElementById('query-input'); input.value = btn.textContent; handleSend(); } // ── Send ────────────────────────────────────────────────────────────────── async function handleSend() { const input = document.getElementById('query-input'); const query = input.value.trim(); if (!query || state.loading) return; input.value = ''; setLoading(true); appendMessage('user', query); const thinkingEl = appendThinking(); try { const res = await fetch(`${API}/query`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, client: state.client }), }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error(err.detail || 'Request failed'); } const data = await res.json(); thinkingEl.remove(); appendBotMessage(data); renderEval(data); } catch (err) { thinkingEl.remove(); appendMessage('bot', `Error: ${err.message}`); } finally { setLoading(false); } } // ── Messages ────────────────────────────────────────────────────────────── function appendMessage(role, text) { const el = document.createElement('div'); el.className = `message ${role}`; el.innerHTML = `
${escapeHtml(text)}
${role === 'user' ? 'You' : 'Bot'}
`; getMessages().appendChild(el); scrollMessages(); return el; } function appendBotMessage(data) { const overall = data.evaluation.overall_pass; const verdictClass = overall ? 'pass' : 'fail'; const failedNames = Object.entries(data.evaluation.metrics) .filter(([, m]) => !m.passed) .map(([k]) => METRIC_LABELS[k] || k) .join(', '); const verdictLabel = overall ? '✓ All checks passed' : `✗ Failed: ${failedNames}`; const flagBanner = data.flagged ? `
⚠ Response flagged — ${failedNames}
` : ''; const el = document.createElement('div'); el.className = 'message bot'; el.innerHTML = ` ${flagBanner}
${escapeHtml(data.answer)}
${verdictLabel}
${data.client_display}
`; getMessages().appendChild(el); scrollMessages(); } function appendThinking() { const wrap = document.createElement('div'); wrap.className = 'message bot'; wrap.innerHTML = `
`; getMessages().appendChild(wrap); scrollMessages(); return wrap; } // ── Eval panel ──────────────────────────────────────────────────────────── const METRIC_LABELS = { pii_leakage: 'PII Leakage', token_budget: 'Token Budget', answer_relevancy: 'Answer Relevancy', faithfulness: 'Faithfulness', chain_terminology: 'Chain Terminology', }; const METRIC_DESC = { pii_leakage: 'Regex scan — no PII in response', token_budget: 'Response within token ceiling', answer_relevancy: 'Cosine similarity: query ↔ response', faithfulness: 'NLI cross-encoder: grounded in retrieved context?', chain_terminology: 'Deterministic: client-specific terms used', }; function renderEval(data) { const metrics = data.evaluation.metrics; const sources = data.sources; const metricCards = Object.entries(metrics).map(([key, m]) => { const cls = scoreClass(m.score, key); const pct = Math.round(m.score * 100); return `
${METRIC_LABELS[key] || key} ${pct}%
${escapeHtml(METRIC_DESC[key] || '')}
${escapeHtml(m.detail)}
`; }).join(''); const sourceItems = sources.map(s => `
${escapeHtml(s.title)} ${(s.score * 100).toFixed(0)}%
`).join(''); document.getElementById('eval-body').innerHTML = `
${metricCards}
Retrieved Sources
${sourceItems || '
No sources retrieved
'}
`; } function scoreClass(score, metric) { // pii_leakage: 1.0 = pass, anything else = fail (binary) if (metric === 'pii_leakage') return score === 1.0 ? 'pass' : 'fail'; if (score >= 0.75) return 'pass'; if (score >= 0.45) return 'warn'; return 'fail'; } // ── Helpers ─────────────────────────────────────────────────────────────── function setLoading(val) { state.loading = val; document.getElementById('send-btn').disabled = val; document.getElementById('query-input').disabled = val; } function getMessages() { return document.getElementById('messages'); } function scrollMessages() { const el = getMessages(); el.scrollTop = el.scrollHeight; } function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); } function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } boot();