// 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.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 += ``; if (a.caution && a.caution.length) h += `

${a.caution.map(esc).join(' ')}

`; if (a.law && a.law.length) h += `

The law

`; if (a.book && a.book.length) h += `

Read more in the CAPGAIN book

`; 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 ? `` : ''); 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 = `
CAPGAIN · Property tax assistant

Ask CAPGAIN AI

Ask in your own words. You get a direct answer from CAPGAIN’s verified law library, your numbers worked out, and where to read more in the book.

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();