Xu ly SSO

This commit is contained in:
2026-05-09 10:31:28 +00:00
parent 9d04e7484c
commit f937d1a98e
21 changed files with 2515 additions and 271 deletions

291
frontend/app.js Normal file
View File

@@ -0,0 +1,291 @@
// API Base URL
const API_BASE = 'http://localhost:8000';
// 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');
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', '<i class="fas fa-spinner fa-spin"></i> 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 = `
<div class="avatar"><i class="fas fa-${icon}"></i></div>
<div class="content">${text}</div>
`;
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, '<br>');
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 = `<i class="fas fa-file-pdf"></i> 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 = `
<h4><i class="fas fa-file-alt"></i> ${src.file_name}</h4>
<p><strong>Vị trí:</strong> Trang ${src.page}</p>
${src.url ? `<a href="${src.url}" target="_blank" style="color: #06b6d4; font-size: 11px; text-decoration: none; display: block; margin-top: 5px;">
<i class="fas fa-external-link-alt"></i> Xem trên SharePoint
</a>` : ''}
${src.download_url ? `<a href="${src.download_url}" target="_blank" style="color: #10b981; font-size: 11px; text-decoration: none; display: block; margin-top: 5px;">
<i class="fas fa-download"></i> Tải xuống
</a>` : ''}
`;
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;
// Init
checkLogin();