// 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"); // API URL is same as frontend (nginx proxies /api and /auth to backend) const API_URL = window.location.origin; 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}`; // Render markdown for bot messages, escape HTML for user messages const content = type === "assistant" ? renderMarkdown(text) : escapeHtml(text); msg.innerHTML = `
${content}
`; chatMessages.appendChild(msg); // Add copy buttons to code blocks for assistant messages if (type === "assistant") { addCopyButtonsToCodeBlocks(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; } function renderMarkdown(text) { // Check if libraries are loaded if (typeof marked === 'undefined' || typeof DOMPurify === 'undefined') { logger.error('Markdown libraries not loaded', { markedAvailable: typeof marked !== 'undefined', dompurifyAvailable: typeof DOMPurify !== 'undefined' }); return escapeHtml(text); } try { // Configure marked.js for optimal chat rendering marked.setOptions({ breaks: true, // Convert line breaks to
gfm: true, // GitHub-flavored markdown headerIds: false, // No IDs for headers mangle: false // Don't obfuscate emails }); // Parse markdown to HTML const rawHtml = marked.parse(text); // Sanitize HTML to prevent XSS attacks (include button element for copy functionality) const cleanHtml = DOMPurify.sanitize(rawHtml, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'hr', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'button'], ALLOWED_ATTR: ['href', 'target', 'rel', 'class'] }); return cleanHtml; } catch (error) { logger.error('Markdown rendering failed', error); return escapeHtml(text); } } function addCopyButtonsToCodeBlocks(element) { // Find all
 elements in the given element
  const preElements = element.querySelectorAll('pre');

  preElements.forEach((pre) => {
    // Skip if button already exists
    if (pre.querySelector('.copy-button')) {
      return;
    }

    // Create copy button
    const button = document.createElement('button');
    button.className = 'copy-button';
    button.textContent = 'Copy';

    // Add click handler
    button.addEventListener('click', async () => {
      const code = pre.querySelector('code');
      const text = code ? code.textContent : pre.textContent;

      try {
        await navigator.clipboard.writeText(text);
        button.textContent = 'Copied!';
        button.classList.add('copied');

        // Reset after 2 seconds
        setTimeout(() => {
          button.textContent = 'Copy';
          button.classList.remove('copied');
        }, 2000);
      } catch (error) {
        logger.error('Failed to copy code to clipboard', error);
        button.textContent = 'Failed';
        setTimeout(() => {
          button.textContent = 'Copy';
        }, 2000);
      }
    });

    // Add button to pre element
    pre.appendChild(button);
  });
}

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.innerHTML = renderMarkdown(assistantMessage);
            scrollToBottom();
          }

          if (data.done) {
            // Add copy buttons to code blocks after streaming is complete
            addCopyButtonsToCodeBlocks(msgDiv);
            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();