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 `
${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();