// CAPGAIN assistant — answers questions from CAPGAIN's verified law library and points to the book by chapter
// and page. The book's own text is never shown (it is not even shipped to the browser).
import { createEngine } from './answer-engine.mjs?v=642';
const $ = (s) => document.querySelector(s);
const esc = (t) => String(t == null ? '' : t).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
const safeUrl = (u) => (/^https:\/\//i.test(String(u || '')) ? String(u) : '');
let enginePromise;
function engine() {
return enginePromise ||= (async () => {
const book = await fetch('book/book-map.json?v=642', { credentials: 'omit' }).then((r) => (r.ok ? r.json() : null)).catch(() => null);
for (let i = 0; i < 40 && !(window.CG && window.CG.LAWLIB_DATA); i++) await new Promise((r) => setTimeout(r, 150));
const records = (window.CG && window.CG.LAWLIB_DATA && window.CG.LAWLIB_DATA.records) || [];
return createEngine({ records, book });
})();
}
function endpoint() {
const c = window.CAPGAIN_BOOK_AI_ENDPOINT;
return typeof c === 'string' && /^https:\/\//i.test(c) ? c : '';
}
const plainRefs = () => !(window.CG && CG.state && CG.state.prefs && CG.state.prefs.proRefs);
const tidy = (t) => (plainRefs() ? String(t).replace(/[;,]?\s*(?:old\s+)?s\.\d+[A-Za-z-]*(?:\(\d+\))?/g, '').replace(/\(\s*[,;]?\s*\)/g, '').replace(/\s{2,}/g, ' ').replace(/\s+([,.;)])/g, '$1') : String(t));
const STATUS = { 'in force': 'In force', amended: 'Amended', superseded: 'Old law (pre-Apr 2026)', verify: 'Being verified', proposed: 'Proposed', pending: 'Pending' };
function render(a) {
if (a.kind === 'greeting' || a.kind === 'scope') return `
${esc(a.text)}
${chips(a.suggestions)}`;
if (a.kind === 'remote') return `${esc(a.text)}
`;
let h = '';
if (a.kind === 'unknown') h += `${esc(a.text)}
`;
if (a.calc) h += `Your numbers
${a.calc.lines.map((l) => `- ${esc(tidy(l))}
`).join('')}
${a.calc.notes.length ? `${a.calc.notes.map((n) => esc(tidy(n))).join(' ')}
` : ''}${a.calc.kind === 'sale' ? 'Estimate before any unused basic-exemption adjustment, surcharge on your other income, and reliefs. Not a substitute for advice on your exact facts.
' : ''}`;
if (a.headline) h += `${esc(tidy(a.headline))}
`;
if (a.points && a.points.length) h += `${a.points.map((p) => `- ${esc(p)}
`).join('')}
`;
if (a.caution && a.caution.length) h += `${a.caution.map(esc).join(' ')}
`;
if (a.law && a.law.length) h += `The law
${a.law.map((l) => { const u = safeUrl(l.url); const plain = !(window.CG && CG.state && CG.state.prefs && CG.state.prefs.proRefs); const label = plain ? (String(l.title || '').split(/\s[-—–]\s/).slice(1).join(' – ') || l.title) : l.ref; return `- ${esc(label)} ${esc(STATUS[l.status] || l.status || '')}${u ? ` Official source ↗` : ''}
`; }).join('')}
`;
if (a.book && a.book.length) h += `Read more in the CAPGAIN book
${a.book.map((b) => `- Chapter ${esc(b.chapter)}${b.chapterTitle ? ` (${esc(b.chapterTitle)})` : ''} · §${esc(b.section)} ${esc(b.title)} · ${esc(b.pages)}
`).join('')}
`;
if (a.tools && a.tools.length) h += `${a.tools.map((t) => ``).join('')}
`;
if (a.related && a.related.length) h += `People also ask
${chips(a.related.map((r) => r.q))}`;
h += `Law as on ${esc(a.asOf || '15 October 2026')}. General information from CAPGAIN’s verified law library — check your own facts before acting.
`;
return h;
}
const chips = (qs) => (qs && qs.length ? `${qs.map((q) => ``).join('')}
` : '');
function build() {
const button = document.createElement('button');
button.type = 'button'; button.className = 'cg-book-nav'; button.textContent = 'Ask CAPGAIN AI';
button.setAttribute('aria-haspopup', 'dialog');
const knowledge = $('#pg-knowledge');
if (knowledge) knowledge.prepend(button); else document.body.appendChild(button);
const dialog = document.createElement('dialog');
dialog.id = 'cg-book-dialog';
dialog.setAttribute('aria-labelledby', 'cg-book-title');
dialog.innerHTML = `Runs on your device — your question is not sent anywhere. General information, not legal or tax advice.
`;
document.body.appendChild(dialog);
const input = $('#cg-book-question'); const status = $('#cg-book-status'); const conv = $('#cg-book-conversation');
const open = (question) => { if (!dialog.open) dialog.showModal(); if (typeof question === 'string' && question.trim()) { input.value = question; ask(question); } else { if (!conv.childElementCount) greet(); input.focus(); } };
button.onclick = () => open();
window.CapGainBook = { open };
const home = $('#pg-home');
if (home) { const hb = button.cloneNode(true); hb.onclick = () => open(); home.prepend(hb); }
dialog.querySelector('.cg-book-close').onclick = () => dialog.close();
dialog.addEventListener('click', (e) => {
if (e.target === dialog) return dialog.close();
const q = e.target.closest && e.target.closest('.cga-ask');
if (q) { input.value = q.dataset.q; return ask(q.dataset.q); }
const t = e.target.closest && e.target.closest('.cga-tool');
if (t) { dialog.close(); try { if (window.CG && CG.openCalc) CG.openCalc(t.dataset.tool); else location.hash = '#calculators'; } catch { location.hash = '#calculators'; } }
});
const history = [];
async function greet() { const e = await engine(); add(null, render(e.answer('hello'))); }
function add(question, html) {
if (question) { const u = document.createElement('div'); u.className = 'cg-book-turn-user'; u.textContent = question; conv.appendChild(u); }
const a = document.createElement('div'); a.className = 'cg-book-answer cga-answer'; a.innerHTML = html; conv.appendChild(a);
a.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
let busy = false;
async function ask(question) {
question = String(question || '').trim(); if (!question || busy) return;
busy = true; status.textContent = 'Thinking…'; dialog.querySelector('#cg-book-form button').disabled = true;
try {
const e = await engine(); let result = e.answer(question);
const ep = endpoint();
if (ep && result.kind !== 'scope') {
try {
const r = await fetch(ep, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ question, history: history.slice(-6) }) });
const j = r.ok ? await r.json() : null;
if (j && typeof j.answer === 'string' && j.answer.trim()) result = { ...result, kind: 'answer', headline: j.answer.slice(0, 2400), points: [] };
} catch { /* stay with the on-device answer */ }
}
history.push({ q: question.slice(0, 300) });
add(question, render(result)); input.value = ''; status.textContent = '';
} catch (err) {
status.textContent = 'The assistant could not load. Please refresh the page and try again.';
} finally { busy = false; dialog.querySelector('#cg-book-form button').disabled = false; input.focus(); }
}
$('#cg-book-form').onsubmit = (e) => { e.preventDefault(); ask(input.value); };
// The home "Ask CapGain" search box answers through the same engine (smart answer, not a list of library hits).
// Navigation commands and document uploads still go to the site's own handler.
window.addEventListener('submit', async (e) => {
const form = e.target;
if (!form || form.id !== 'home-ask') return;
const q = (form.querySelector('#ask-q') || {}).value || '';
if (!q.trim() || /^(open|go to|show me the|take me|launch)\b/i.test(q.trim())) return;
const tray = document.getElementById('ask-tray');
if (tray && tray.querySelector('img, .ask-file, [data-file], li')) return;
e.preventDefault(); e.stopImmediatePropagation();
const eng = await engine();
const probe = eng.answer(q); eng.reset();
if (probe.kind === 'scope' && window.CG && CG.ai && typeof CG.ai.ask === 'function') { try { CG.ai.ask(q); return; } catch { /* fall through */ } }
open(q);
}, true);
// Relabel the site's entry points to the new assistant.
const relabel = () => {
for (const b of document.querySelectorAll('[data-native-action="assistant"] span:not(.cg-native-search-icon)')) if (/book/i.test(b.textContent)) b.textContent = 'Ask CAPGAIN AI — tax, TDS, NRI, RERA';
const fab = document.getElementById('cg-native-ai-fab');
if (fab) { fab.setAttribute('aria-label', 'Ask CAPGAIN AI'); const sp = fab.querySelector('span'); if (sp && /book/i.test(sp.textContent)) sp.textContent = 'Ask AI'; }
};
relabel(); setTimeout(relabel, 800); setTimeout(relabel, 2500);
new MutationObserver(() => relabel()).observe(document.body, { childList: true });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', build, { once: true }); else build();