/* global React */ /* InsightBox — live AI summary of a chart. The page passes the SAME numbers the chart renders (`facts`), so the summary is always faithful to what's on screen and reflects the selected period. POSTs to /api/insight (Claude, Turkish, quantified). Client-side cache + debounce keep it cheap; graceful demo-mode fallback if the backend is down. */ const _insightCache = new Map(); // key -> summary string // Minimal inline markdown: **bold**, _em_, and "- " bullets (matches copilot). const _renderMd = (text) => { const inline = (s) => s .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/(^|[^_])_(.+?)_/g, '$1$2'); const lines = (text || '').split('\n').filter(l => l.trim().length); return lines.map((line, i) => { const t = line.trim(); if (t.startsWith('- ') || t.startsWith('• ')) { return React.createElement('div', { key: i, className: 'insight-li', dangerouslySetInnerHTML: { __html: inline(t.slice(2)) } }); } return React.createElement('div', { key: i, style: { marginBottom: 3 }, dangerouslySetInnerHTML: { __html: inline(t) } }); }); }; const InsightBox = ({ label, hint = '', facts, compact = false }) => { const [state, setState] = useState({ status: 'idle', summary: '' }); const factsKey = useMemo(() => { try { return label + '::' + JSON.stringify(facts); } catch (e) { return label + '::?'; } }, [label, facts]); useEffect(() => { let alive = true; if (_insightCache.has(factsKey)) { setState({ status: 'done', summary: _insightCache.get(factsKey) }); return; } setState({ status: 'loading', summary: '' }); // Debounce so dragging the period filter doesn't fire a call per frame. const timer = setTimeout(async () => { try { const base = (typeof window !== 'undefined' && window.API_BASE_URL) || ''; const res = await fetch(base + '/api/insight', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ label, hint, facts }), }); if (!res.ok) throw new Error('HTTP ' + res.status); const data = await res.json(); if (!alive) return; _insightCache.set(factsKey, data.summary || ''); setState({ status: data.configured === false ? 'demo' : 'done', summary: data.summary || '' }); } catch (e) { if (alive) setState({ status: 'demo', summary: '' }); } }, 550); return () => { alive = false; clearTimeout(timer); }; }, [factsKey]); const eyebrow = React.createElement('div', { className: 'insight-eyebrow' }, React.createElement('span', { className: 'insight-dot' }), 'AI ÖZET', React.createElement('span', { className: 'insight-by' }, 'Claude · canlı')); let body; if (state.status === 'loading' || state.status === 'idle') { body = React.createElement('div', { className: 'insight-loading' }, React.createElement('span', { className: 'insight-shimmer' }), 'analiz yazılıyor…'); } else if (state.status === 'demo') { body = React.createElement('div', { className: 'insight-demo' }, 'Bu grafiğin yapay zekâ özeti, canlı veri ambarına bağlandığında otomatik üretilir. ' + '(Demo modu — Copilot servisi şu anda kapalı.)'); } else { body = React.createElement('div', { className: 'insight-text' }, _renderMd(state.summary)); } return React.createElement('div', { className: 'insight-box' + (compact ? ' insight-compact' : '') }, eyebrow, body); }; window.InsightBox = InsightBox;