/* global React, DATA */ const Copilot = ({ open, onClose }) => { const [messages, setMessages] = useState([ { role: 'ai', t: `Günaydın Kaan Bey. ${fmtN(DATA.hotel_index.length)} otel, ${fmtN(DATA.flights_kpi_2025.flights)} uçuş, ${fmtN((DATA.top_recommendations || []).length)} öneri görüyorum. İstediğini sor — ambarı doğrudan sorgulayacağım.` } ]); const [input, setInput] = useState(''); const [thinking, setThinking] = useState(false); const endRef = useRef(); useEffect(() => { endRef.current && endRef.current.scrollTo({ top: 99999, behavior: 'smooth' }); }, [messages, thinking]); const send = async (text) => { if (!text.trim() || thinking) return; const userMsg = { role: 'user', t: text }; const next = [...messages, userMsg]; setMessages(next); setInput(''); setThinking(true); try { const apiMessages = next.map(m => ({ role: m.role === 'ai' ? 'assistant' : 'user', content: m.t, })); const base = (typeof window !== 'undefined' && window.API_BASE_URL) || ''; // SSE streaming: render tokens as they arrive; tool turns show a status line. const res = await fetch(base + '/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: apiMessages }), }); if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`); let acc = '', toolCalls = [], status = null; setMessages(m => [...m, { role: 'ai', t: '', streaming: true }]); const paint = () => setMessages(m => { const copy = m.slice(); copy[copy.length - 1] = { role: 'ai', t: acc || (status ? '_' + status + '_' : ''), tool_calls: toolCalls, streaming: true }; return copy; }); const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); const parts = buf.split('\n\n'); buf = parts.pop(); for (const part of parts) { if (!part.startsWith('data: ')) continue; let ev; try { ev = JSON.parse(part.slice(6)); } catch (e) { continue; } if (ev.type === 'delta') { acc += ev.data; paint(); } else if (ev.type === 'status') { status = ev.data; if (!acc) paint(); } else if (ev.type === 'tools') { toolCalls = ev.data || []; } else if (ev.type === 'error') { acc += '\n\n_Hata: ' + ev.data + '_'; paint(); } } } setMessages(m => { const copy = m.slice(); copy[copy.length - 1] = { role: 'ai', t: acc || '(yanıt yok)', tool_calls: toolCalls }; return copy; }); } catch (e) { // On mid-stream failure REPLACE the streaming placeholder, don't append. setMessages(m => { const copy = m.slice(); const err = `_Masaya ulaşılamadı:_ ${e.message}`; if (copy.length && copy[copy.length - 1].streaming) copy[copy.length - 1] = { role: 'ai', t: err }; else copy.push({ role: 'ai', t: err }); return copy; }); } finally { setThinking(false); } }; const quick = [ 'gelir bazında en iyi 5 Belek 5★ otel', 'Bodrum 2026 tempo durumu', 'Rusya uçuş doluluk oranı aya göre', 'bu hafta en büyük fiyat önerileri', 'en düşük marjlı oteller', ]; if (!open) return null; const fmtMsg = (t) => t.split('\n').map((line, i) => { if (line.startsWith('• ')) return React.createElement('div', { key: i, style: { paddingLeft: 10 } }, line); return React.createElement('div', { key: i, dangerouslySetInnerHTML: { __html: line.replace(/\*\*(.+?)\*\*/g, '$1').replace(/_(.+?)_/g, '$1') } }); }); return React.createElement('aside', { className: 'copilot-panel', style: { width: 'var(--copilot-w)', flexShrink: 0, borderLeft: '1px solid var(--rule-soft)', background: 'var(--paper-soft)', display: 'flex', flexDirection: 'column', height: '100%' } }, React.createElement('div', { style: { padding: '12px 14px', borderBottom: '1px solid var(--rule-soft)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, React.createElement('div', null, React.createElement('div', { className: 'row gap-2' }, React.createElement('span', { className: 'dot dot-pos' }), React.createElement('span', { style: { fontWeight: 700, fontSize: 12 } }, 'RM Copilot') ), React.createElement('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2 } }, 'Anthropic Claude · ambarınıza dayalı') ), React.createElement('button', { className: 'btn btn-sm btn-ghost', onClick: onClose, title: 'Kapat (⌘ /)' }, '✕') ), React.createElement('div', { ref: endRef, style: { flex: 1, overflowY: 'auto', padding: '14px 18px', display: 'flex', flexDirection: 'column', gap: 10, width: '100%', maxWidth: 920, margin: '0 auto', boxSizing: 'border-box' } }, messages.map((m, i) => (m.streaming && !m.t) ? null : React.createElement('div', { key: i, style: { alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start', maxWidth: '88%', padding: '11px 14px', fontSize: 14, lineHeight: 1.6, borderRadius: 4, background: m.role === 'user' ? 'var(--ink)' : 'var(--paper)', color: m.role === 'user' ? 'var(--paper)' : 'var(--ink)', border: m.role === 'user' ? 'none' : '1px solid var(--rule-soft)', whiteSpace: 'pre-wrap' } }, fmtMsg(m.t))), thinking && !(messages.length && messages[messages.length - 1].streaming && messages[messages.length - 1].t) && React.createElement('div', { style: { alignSelf: 'flex-start', padding: '10px 13px', fontSize: 13, color: 'var(--text-muted)', fontStyle: 'italic' } }, '· · · ambar sorgulanıyor') ), React.createElement('div', { style: { padding: '8px 10px', borderTop: '1px solid var(--rule-soft)', display: 'flex', flexWrap: 'wrap', gap: 4, width: '100%', maxWidth: 920, margin: '0 auto', boxSizing: 'border-box' } }, quick.map((q, i) => React.createElement('button', { key: i, className: 'btn btn-sm btn-ghost', style: { fontSize: 12 }, onClick: () => send(q) }, q)) ), React.createElement('div', { style: { padding: 10, borderTop: '1px solid var(--rule-soft)', display: 'flex', gap: 6, width: '100%', maxWidth: 920, margin: '0 auto', boxSizing: 'border-box' } }, React.createElement('input', { className: 'input', style: { flex: 1 }, placeholder: 'İstediğini sor…', value: input, onChange: e => setInput(e.target.value), onKeyDown: e => e.key === 'Enter' && send(input), disabled: thinking }), React.createElement('button', { className: 'btn btn-sm btn-primary', onClick: () => send(input), disabled: thinking }, 'Gönder') ) ); }; window.Copilot = Copilot;