// API Base URL (relative khi served từ backend) const API_BASE = window.location.origin; // DOM Elements const loginScreen = document.getElementById('login-screen'); const appContainer = document.getElementById('app-container'); const loginForm = document.getElementById('login-form'); const loginEmail = document.getElementById('login-email'); const chatWindow = document.getElementById('chat-window'); const userInput = document.getElementById('user-input'); const sendBtn = document.getElementById('send-btn'); const sourcePanel = document.getElementById('source-panel'); const sourceList = document.getElementById('source-list'); const closePanel = document.getElementById('close-panel'); const clearChatBtn = document.getElementById('clear-chat'); const userName = document.getElementById('user-name'); const userRole = document.getElementById('user-role'); const logoutBtn = document.getElementById('logout-btn'); const syncBtn = document.getElementById('sync-btn'); const syncStatus = document.getElementById('sync-status'); const ssoBtn = document.getElementById('sso-btn'); const historyBtn = document.getElementById('history-btn'); const historyPanel = document.getElementById('history-panel'); const historyList = document.getElementById('history-list'); const closeHistory = document.getElementById('close-history'); let chatHistory = []; let currentUser = null; // ====== AUTH ====== function checkLogin() { // Kiểm tra SSO callback (user data trong URL) const params = new URLSearchParams(window.location.search); const userData = params.get('user'); if (userData) { try { currentUser = JSON.parse(decodeURIComponent(userData)); localStorage.setItem('vibecode_user', JSON.stringify(currentUser)); window.history.replaceState({}, '', '/'); // Xóa query param showApp(); return; } catch (e) { console.error('Parse SSO user data failed:', e); } } // Kiểm tra localStorage const saved = localStorage.getItem('vibecode_user'); if (saved) { currentUser = JSON.parse(saved); showApp(); } } function showApp() { loginScreen.style.display = 'none'; appContainer.style.display = 'flex'; userName.textContent = currentUser.display_name; userRole.textContent = currentUser.role; } function showLogin() { loginScreen.style.display = 'flex'; appContainer.style.display = 'none'; currentUser = null; localStorage.removeItem('vibecode_user'); } loginForm.onsubmit = async (e) => { e.preventDefault(); const email = loginEmail.value.trim(); if (!email) return; try { const response = await fetch(`${API_BASE}/auth/login-email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); if (!response.ok) { const err = await response.json(); alert(err.detail || 'Đăng nhập thất bại'); return; } currentUser = await response.json(); localStorage.setItem('vibecode_user', JSON.stringify(currentUser)); showApp(); } catch (error) { console.error('Login error:', error); alert('Không thể kết nối tới server. Vui lòng đảm bảo Backend đang chạy.'); } }; logoutBtn.onclick = () => { chatHistory = []; chatWindow.innerHTML = ''; showLogin(); }; // SSO Login ssoBtn.onclick = () => { window.location.href = `${API_BASE}/auth/login`; }; // ====== CHAT ====== // Tự động giãn nở ô nhập liệu userInput.addEventListener('input', () => { userInput.style.height = 'auto'; userInput.style.height = (userInput.scrollHeight) + 'px'; }); async function sendMessage() { const text = userInput.value.trim(); if (!text) return; appendMessage('user', text); userInput.value = ''; userInput.style.height = 'auto'; const loadingId = appendMessage('ai', ' AI đang phân tích dữ liệu...'); try { const headers = { 'Content-Type': 'application/json' }; if (currentUser) { headers['X-User-Email'] = currentUser.email; headers['X-User-Role'] = currentUser.role; } const response = await fetch(`${API_BASE}/chat`, { method: 'POST', headers: headers, body: JSON.stringify({ query: text, history: chatHistory }) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const data = await response.json(); updateMessage(loadingId, data.answer, data.sources); chatHistory.push({ role: 'user', content: text }); chatHistory.push({ role: 'assistant', content: data.answer }); if (chatHistory.length > 6) chatHistory = chatHistory.slice(-6); } catch (error) { console.error("Chat error:", error); updateMessage(loadingId, "⚠️ Lỗi kết nối tới máy chủ AI. Vui lòng đảm bảo Backend đang chạy tại port 8000."); } } function appendMessage(role, text) { const id = Date.now(); const msgDiv = document.createElement('div'); msgDiv.className = `message ${role}`; msgDiv.id = `msg-${id}`; const icon = role === 'ai' ? 'robot' : 'user'; msgDiv.innerHTML = `
${text}
`; chatWindow.appendChild(msgDiv); chatWindow.scrollTop = chatWindow.scrollHeight; return id; } function updateMessage(id, text, sources = []) { const msgDiv = document.getElementById(`msg-${id}`); if (!msgDiv) return; const contentDiv = msgDiv.querySelector('.content'); contentDiv.innerHTML = text.replace(/\n/g, '
'); if (sources && sources.length > 0) { const tagDiv = document.createElement('div'); tagDiv.style.marginTop = '15px'; tagDiv.style.display = 'flex'; tagDiv.style.gap = '8px'; tagDiv.style.flexWrap = 'wrap'; sources.forEach((src, idx) => { const tag = document.createElement('span'); tag.className = 'citation-tag'; tag.innerHTML = ` Nguồn ${idx + 1}`; tag.onclick = (e) => { e.stopPropagation(); showSources(sources); }; tagDiv.appendChild(tag); }); contentDiv.appendChild(tagDiv); } chatWindow.scrollTop = chatWindow.scrollHeight; } function showSources(sources) { sourceList.innerHTML = ''; sources.forEach(src => { const item = document.createElement('div'); item.className = 'source-item'; item.innerHTML = `

${src.file_name}

Vị trí: Trang ${src.page}

${src.url ? ` Xem trên SharePoint ` : ''} ${src.download_url ? ` Tải xuống ` : ''} `; sourceList.appendChild(item); }); sourcePanel.classList.add('active'); } // Event Listeners closePanel.onclick = () => sourcePanel.classList.remove('active'); sendBtn.onclick = sendMessage; userInput.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; clearChatBtn.onclick = () => { chatWindow.innerHTML = ''; chatHistory = []; appendMessage('ai', 'Lịch sử chat đã được làm sạch. Tôi có thể giúp gì tiếp cho bạn?'); }; // ====== SYNC ====== async function triggerSync() { syncBtn.disabled = true; syncStatus.style.display = 'flex'; syncStatus.className = 'sync-status'; syncStatus.querySelector('.sync-text').textContent = 'Đang đồng bộ...'; try { const response = await fetch(`${API_BASE}/sync`, { method: 'POST' }); const data = await response.json(); if (data.status === 'already_running') { syncStatus.querySelector('.sync-text').textContent = 'Đồng bộ đang chạy...'; } else { syncStatus.querySelector('.sync-text').textContent = 'Đang xử lý...'; pollSyncStatus(); } } catch (error) { syncStatus.className = 'sync-status error'; syncStatus.querySelector('.sync-text').textContent = 'Lỗi kết nối server'; syncBtn.disabled = false; } } async function pollSyncStatus() { try { const response = await fetch(`${API_BASE}/sync/status`); const data = await response.json(); if (data.running) { const count = data.processed + data.skipped; syncStatus.querySelector('.sync-text').textContent = `Đang xử lý... (${count} file)`; setTimeout(pollSyncStatus, 2000); } else { syncStatus.className = 'sync-status done'; syncStatus.querySelector('.sync-text').textContent = `Xong! ${data.processed} file đã nạp, ${data.skipped} bỏ qua`; syncBtn.disabled = false; setTimeout(() => { syncStatus.style.display = 'none'; }, 5000); } } catch (error) { syncStatus.className = 'sync-status error'; syncStatus.querySelector('.sync-text').textContent = 'Lỗi kiểm tra trạng thái'; syncBtn.disabled = false; } } syncBtn.onclick = triggerSync; // ====== SYNC HISTORY ====== async function loadSyncHistory() { try { const response = await fetch(`${API_BASE}/sync/history?limit=20`); const data = await response.json(); historyList.innerHTML = ''; if (!data.runs || data.runs.length === 0) { historyList.innerHTML = '

Chưa có lần đồng bộ nào.

'; return; } data.runs.forEach(run => { const item = document.createElement('div'); item.className = 'history-item'; const time = new Date(run.started_at).toLocaleString('vi-VN'); const statusClass = run.status; const statusText = run.status === 'completed' ? 'Hoàn thành' : run.status === 'failed' ? 'Thất bại' : 'Đang chạy'; item.innerHTML = `
${time} ${statusText}
${run.summary.processed} nạp ${run.summary.skipped} bỏ ${run.summary.errors} lỗi
${run.files.map(f => `
${f.name} ${f.chunks > 0 ? f.chunks + ' chunks' : f.status}
`).join('')} ${run.errors.length > 0 ? `
${run.errors.map(e => `
⚠ ${e}
`).join('')}
` : ''}
`; item.onclick = () => item.classList.toggle('expanded'); historyList.appendChild(item); }); } catch (error) { console.error('Load history error:', error); historyList.innerHTML = '

Lỗi tải lịch sử.

'; } } historyBtn.onclick = () => { historyPanel.classList.toggle('active'); if (historyPanel.classList.contains('active')) { loadSyncHistory(); } }; closeHistory.onclick = () => historyPanel.classList.remove('active'); // Init checkLogin();