// Enhanced logging utility class Logger { constructor() { this.logs = []; this.maxLogs = 1000; } log(level, message, data = null) { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level, message, data, url: window.location.href }; this.logs.push(logEntry); if (this.logs.length > this.maxLogs) { this.logs.shift(); } const consoleMethod = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'; console[consoleMethod](`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || ''); // Save to localStorage for persistence try { localStorage.setItem('devden_logs', JSON.stringify(this.logs)); } catch (e) { console.warn('Failed to save logs to localStorage:', e); } } info(message, data = null) { this.log('info', message, data); } warn(message, data = null) { this.log('warn', message, data); } error(message, data = null) { this.log('error', message, data); } exportLogs() { const blob = new Blob([JSON.stringify(this.logs, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `devden_logs_${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); } clearLogs() { this.logs = []; localStorage.removeItem('devden_logs'); } } const logger = new Logger(); // Load existing logs from localStorage try { const savedLogs = localStorage.getItem('devden_logs'); if (savedLogs) { logger.logs = JSON.parse(savedLogs); logger.info('Loaded existing logs from localStorage', { count: logger.logs.length }); } } catch (e) { logger.warn('Failed to load existing logs from localStorage:', e); } const loginScreen = document.getElementById("loginScreen"); const welcomeScreen = document.getElementById("welcomeScreen"); const chatScreen = document.getElementById("chatScreen"); const chatMessages = document.getElementById("chatMessages"); const welcomeInput = document.getElementById("welcomeInput"); const chatInput = document.getElementById("chatInput"); const loginBtn = document.getElementById("loginBtn"); const API_URL = "http://localhost:8000"; let isInChat = false; // Auth functions function getToken() { try { const token = localStorage.getItem("devden_token"); logger.info('Retrieved token from localStorage', { hasToken: !!token }); return token; } catch (error) { logger.error('Failed to retrieve token from localStorage', error); return null; } } function setToken(token) { try { localStorage.setItem("devden_token", token); logger.info('Token saved to localStorage'); } catch (error) { logger.error('Failed to save token to localStorage', error); } } function clearToken() { try { localStorage.removeItem("devden_token"); logger.info('Token cleared from localStorage'); } catch (error) { logger.error('Failed to clear token from localStorage', error); } } function showLoginScreen() { try { loginScreen.classList.remove("hidden"); welcomeScreen.classList.add("hidden"); chatScreen.classList.add("hidden"); logger.info('Switched to login screen'); } catch (error) { logger.error('Failed to show login screen', error); } } function showWelcomeScreen() { try { loginScreen.classList.add("hidden"); welcomeScreen.classList.remove("hidden"); chatScreen.classList.add("hidden"); welcomeInput.focus(); logger.info('Switched to welcome screen'); } catch (error) { logger.error('Failed to show welcome screen', error); } } function switchToChat() { try { loginScreen.classList.add("hidden"); welcomeScreen.classList.add("hidden"); chatScreen.classList.remove("hidden"); chatInput.focus(); isInChat = true; logger.info('Switched to chat screen'); } catch (error) { logger.error('Failed to switch to chat screen', error); } } async function checkAuth() { logger.info('Starting auth check'); const token = getToken(); if (!token) { logger.info('No token found, showing login screen'); showLoginScreen(); return; } try { logger.info('Making auth check request to /api/auth/me'); const response = await fetch(`${API_URL}/api/auth/me`, { headers: { Authorization: `Bearer ${token}` }, }); logger.info('Auth check response received', { status: response.status, statusText: response.statusText, ok: response.ok }); if (response.ok) { const userData = await response.json(); logger.info('Auth check successful', { user: userData }); showWelcomeScreen(); } else { const errorText = await response.text(); logger.warn('Auth check failed', { status: response.status, statusText: response.statusText, response: errorText }); clearToken(); showLoginScreen(); } } catch (error) { logger.error('Auth check request failed', { error: error.message, stack: error.stack }); showLoginScreen(); } } async function handleLogin() { logger.info('Login button clicked, starting login process'); try { loginBtn.disabled = true; loginBtn.textContent = "Checking auth config..."; logger.info('Disabled login button and updated text'); // Check if auth is configured logger.info('Checking auth configuration via /api/auth/status'); const statusResponse = await fetch(`${API_URL}/api/auth/status`); const statusData = await statusResponse.json(); logger.info('Auth status response', { status: statusResponse.status, configured: statusData.configured }); if (!statusData.configured) { const errorMsg = "Authentication not configured. Please set ENTRA_TENANT_ID, ENTRA_CLIENT_ID, and ENTRA_CLIENT_SECRET in your .env file."; logger.error('Auth not configured', { response: statusData }); alert(errorMsg); loginBtn.disabled = false; loginBtn.textContent = "Sign in with Microsoft"; return; } loginBtn.textContent = "Getting auth URL..."; logger.info('Auth configured, requesting login URL from /api/auth/login'); // Get auth URL and redirect const response = await fetch(`${API_URL}/api/auth/login`); const data = await response.json(); logger.info('Login URL response', { status: response.status, hasAuthUrl: !!data.auth_url, authUrl: data.auth_url ? data.auth_url.substring(0, 100) + '...' : null }); if (data.auth_url) { loginBtn.textContent = "Redirecting..."; logger.info('Redirecting to Microsoft OAuth URL'); window.location.href = data.auth_url; } else { throw new Error("No auth URL returned"); } } catch (error) { logger.error('Login process failed', { error: error.message, stack: error.stack }); alert("Login failed: " + error.message); loginBtn.disabled = false; loginBtn.textContent = "Sign in with Microsoft"; } } async function handleCallback() { logger.info('Starting OAuth callback processing', { url: window.location.href }); const params = new URLSearchParams(window.location.search); const code = params.get("code"); const sessionState = params.get("session_state"); const error = params.get("error"); const errorDescription = params.get("error_description"); logger.info('Parsed URL parameters', { hasCode: !!code, hasSessionState: !!sessionState, hasError: !!error, codeLength: code ? code.length : 0, sessionState: sessionState, error: error, errorDescription: errorDescription }); // Check for OAuth errors if (error) { logger.error('OAuth error in callback URL', { error: error, errorDescription: errorDescription }); alert(`Authentication failed: ${error} - ${errorDescription || 'Unknown error'}`); return false; } if (!code) { logger.warn('No authorization code found in callback URL'); return false; } try { logger.info('Sending authorization code to backend /api/auth/callback'); const response = await fetch(`${API_URL}/api/auth/callback`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code }), }); logger.info('Callback response received', { status: response.status, statusText: response.statusText, ok: response.ok }); if (!response.ok) { const errorData = await response.json(); logger.error('Callback request failed', { status: response.status, error: errorData }); throw new Error(errorData.detail || "Callback failed"); } const data = await response.json(); logger.info('Callback successful', { hasToken: !!data.token, hasUser: !!data.user, user: data.user }); setToken(data.token); // Clean up URL logger.info('Cleaning up URL (removing query parameters)'); window.history.replaceState({}, "", "/"); return true; } catch (error) { logger.error('Callback processing failed', { error: error.message, stack: error.stack }); alert("Authentication failed: " + error.message); return false; } } // Chat functions function addMessage(text, type = "user") { const msg = document.createElement("div"); msg.className = `message ${type}`; msg.innerHTML = `
${escapeHtml(text)}
`; chatMessages.appendChild(msg); scrollToBottom(); } function showTyping() { const typing = document.createElement("div"); typing.className = "message assistant"; typing.id = "typing"; typing.innerHTML = `
`; chatMessages.appendChild(typing); scrollToBottom(); } function hideTyping() { const typing = document.getElementById("typing"); if (typing) typing.remove(); } function scrollToBottom() { chatMessages.scrollTop = chatMessages.scrollHeight; } function escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } async function sendMessage(text) { if (!text.trim()) return; if (!isInChat) { switchToChat(); } addMessage(text, "user"); chatInput.value = ""; chatInput.disabled = true; showTyping(); try { const token = getToken(); const response = await fetch(`${API_URL}/api/chat/stream`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ message: text, provider: null, }), }); if (response.status === 401) { hideTyping(); clearToken(); showLoginScreen(); return; } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } hideTyping(); // Create message element for streaming const msgDiv = document.createElement("div"); msgDiv.className = "message assistant"; const textDiv = document.createElement("div"); textDiv.className = "message-text"; msgDiv.appendChild(textDiv); chatMessages.appendChild(msgDiv); // Read stream const reader = response.body.getReader(); const decoder = new TextDecoder(); let assistantMessage = ""; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split("\n"); for (const line of lines) { if (line.startsWith("data: ")) { const data = JSON.parse(line.slice(6)); if (data.error) { textDiv.textContent = `Error: ${data.error}`; break; } if (data.chunk) { assistantMessage += data.chunk; textDiv.textContent = assistantMessage; scrollToBottom(); } if (data.done) { break; } } } } } catch (error) { hideTyping(); addMessage(`Error: ${error.message}`, "assistant"); } finally { chatInput.disabled = false; chatInput.focus(); } } // Event listeners loginBtn.addEventListener("click", handleLogin); welcomeInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); const text = welcomeInput.value.trim(); if (text) { sendMessage(text); } } }); chatInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); sendMessage(chatInput.value); } }); // Initialize async function init() { logger.info('Application initialization started', { userAgent: navigator.userAgent, url: window.location.href, timestamp: new Date().toISOString() }); // Check for CSS loading const styleLink = document.querySelector('link[rel="stylesheet"]'); if (styleLink) { logger.info('CSS link found', { href: styleLink.href }); styleLink.addEventListener('load', () => { logger.info('CSS loaded successfully'); }); styleLink.addEventListener('error', (e) => { logger.error('CSS failed to load', { href: styleLink.href, error: e }); }); } else { logger.error('CSS link not found in document'); } // Check DOM elements const elements = ['loginScreen', 'welcomeScreen', 'chatScreen', 'chatMessages', 'welcomeInput', 'chatInput', 'loginBtn']; elements.forEach(id => { const el = document.getElementById(id); if (!el) { logger.error(`Required DOM element not found: ${id}`); } else { logger.info(`DOM element found: ${id}`); } }); try { // Check for OAuth callback first logger.info('Checking for OAuth callback parameters'); const callbackSuccess = await handleCallback(); if (callbackSuccess) { logger.info('OAuth callback processed successfully, showing welcome screen'); showWelcomeScreen(); } else { logger.info('No OAuth callback or callback failed, checking existing auth'); await checkAuth(); } } catch (error) { logger.error('Initialization failed', { error: error.message, stack: error.stack }); } logger.info('Application initialization completed'); } // Add global error handler window.addEventListener('error', (event) => { logger.error('Global JavaScript error', { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error }); }); window.addEventListener('unhandledrejection', (event) => { logger.error('Unhandled promise rejection', { reason: event.reason, promise: event.promise }); }); // Debug panel functionality const debugPanel = document.getElementById('debugPanel'); const exportLogsBtn = document.getElementById('exportLogsBtn'); const clearLogsBtn = document.getElementById('clearLogsBtn'); const viewBackendLogsBtn = document.getElementById('viewBackendLogsBtn'); const closeDebugBtn = document.getElementById('closeDebugBtn'); const debugOutput = document.getElementById('debugOutput'); function showDebugPanel() { debugPanel.style.display = 'block'; logger.info('Debug panel opened'); } function hideDebugPanel() { debugPanel.style.display = 'none'; logger.info('Debug panel closed'); } function updateDebugOutput(text) { debugOutput.textContent = text; debugOutput.scrollTop = debugOutput.scrollHeight; } // Event listeners for debug panel exportLogsBtn.addEventListener('click', () => { logger.exportLogs(); updateDebugOutput('Frontend logs exported to download'); logger.info('Logs exported via debug panel'); }); clearLogsBtn.addEventListener('click', () => { logger.clearLogs(); updateDebugOutput('Logs cleared'); logger.info('Logs cleared via debug panel'); }); viewBackendLogsBtn.addEventListener('click', async () => { try { updateDebugOutput('Loading backend logs...'); const response = await fetch(`${API_URL}/logs`); if (response.ok) { const data = await response.json(); if (data.logs) { updateDebugOutput(data.logs.join('\n')); } else if (data.error) { updateDebugOutput(`Error: ${data.error}`); } } else { updateDebugOutput(`Failed to load backend logs: ${response.status}`); } } catch (error) { updateDebugOutput(`Error loading backend logs: ${error.message}`); logger.error('Failed to load backend logs', error); } }); closeDebugBtn.addEventListener('click', hideDebugPanel); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl+Shift+L: Export logs if (e.ctrlKey && e.shiftKey && e.key === 'L') { e.preventDefault(); logger.exportLogs(); logger.info('Logs exported via keyboard shortcut'); } // Ctrl+Shift+D: Toggle debug panel if (e.ctrlKey && e.shiftKey && e.key === 'D') { e.preventDefault(); if (debugPanel.style.display === 'none') { showDebugPanel(); } else { hideDebugPanel(); } } // Escape: Close debug panel if (e.key === 'Escape' && debugPanel.style.display !== 'none') { hideDebugPanel(); } }); init();