/** * IT Site Survey AI - Frontend Application * Single-page application with Surveys List, Create Survey, and AI Analysis tabs */ // ============================================ // State Management // ============================================ const AppState = { currentTab: 'surveys', currentSurveyId: null, surveys: [], surveyTemplate: null, uploadedPhotos: [], analysisContent: '', deleteTargetId: null, isGenerating: false }; // ============================================ // API Configuration // ============================================ const API_BASE_URL = window.location.origin; // ============================================ // Sample Data for Testing // ============================================ const SAMPLE_DATA = { name: "Acme Corporation Site Survey", client: "Acme Corporation", site: "Main Headquarters - San Francisco", description: "Comprehensive IT infrastructure assessment for digital transformation initiative. Planning cloud migration and security modernization.", model: "gpt-4o", answers: { "q1": "450 employees", "q2": "3 locations (SF HQ, Denver Office, Austin Office)", "q3": "1) Migrate 80% of workloads to cloud by end of 2025\n2) Implement Zero Trust security architecture\n3) Deploy AI-powered customer service platform", "q4": "Hybrid topology with core-distribution-access layers at HQ", "q5": "24 physical servers, 120 VMs", "q6": "VMware vSphere 7.0 with vCenter", "q7": "150TB usable, currently 78% utilized", "q8": "Single internet circuit at Denver office, no redundant power in Austin server room", "q9": "1Gbps symmetric at HQ, 500Mbps at Denver, 300Mbps at Austin", "q10": "45 switches, 8 routers (Cisco Catalyst and ISR series)", "q11": "Planning", "q12": "Mix of Wi-Fi 5 (802.11ac) and Wi-Fi 6 (802.11ax)", "q13": "12 VLANs - segmented by department (Finance, HR, Engineering, Sales, etc.)", "q14": "Palo Alto Networks PA-5220", "q15": "Yes", "q16": "CrowdStrike Falcon", "q17": "Yes - Critical Only", "q18": "6 months ago - Medium risk findings related to outdated Java applications", "q19": "Yes - Not Tested", "q20": "Veeam Backup & Replication, RTO: 4 hours, RPO: 1 hour", "q21": "AWS (primary), Azure (secondary for AD and O365)", "q22": "30% cloud, 70% on-premises", "q23": "Yes - Basic", "q24": "~$45,000/month - Optimization in progress with Reserved Instances", "q25": "1. SAP ERP\n2. Salesforce CRM\n3. Microsoft 365\n4. Custom e-commerce platform\n5. Workday HCM\n6. ServiceNow ITSM\n7. Confluence\n8. Jira\n9. Zoom\n10. Internal BI platform", "q26": "Legacy AS/400 system for inventory, needs API layer or replacement", "q27": "Microsoft Teams, Slack for engineering, Zoom for video", "q28": "Yes - Partial", "q29": "SOC2 Type II, ISO 27001, PCI-DSS (for e-commerce)", "q30": "Last SOC2 audit completed 2 months ago with minor findings remediated", "q31": "Yes - Implemented", "q32": "ServiceNow ITSM, Jira Service Management for dev teams", "q33": "1:75 ratio (6 IT staff for 450 users)", "q34": "4.2 hours average for L2 tickets, 15 minutes for L1", "q35": "1) Complete cloud migration\n2) Implement SD-WAN across all sites\n3) Deploy AI-driven security operations center" }, photos: [] }; // ============================================ // DOM Elements Cache // ============================================ const DOM = {}; function cacheDOMElements() { // Tabs DOM.tabButtons = document.querySelectorAll('.tab-btn'); DOM.tabContents = document.querySelectorAll('.tab-content'); // Navigation DOM.navSurveys = document.getElementById('nav-surveys'); DOM.navCreate = document.getElementById('nav-create'); // Tab content sections DOM.tabSurveys = document.getElementById('tab-surveys'); DOM.tabCreate = document.getElementById('tab-create'); DOM.tabAnalysis = document.getElementById('tab-analysis'); // Surveys List DOM.surveysList = document.getElementById('surveys-list'); DOM.btnClearAll = document.getElementById('btn-clear-all'); // Create Survey Form DOM.surveyForm = document.getElementById('survey-form'); DOM.surveyName = document.getElementById('survey-name'); DOM.surveyClient = document.getElementById('survey-client'); DOM.surveySite = document.getElementById('survey-site'); DOM.surveyDescription = document.getElementById('survey-description'); DOM.surveyModel = document.getElementById('survey-model'); DOM.questionsContainer = document.getElementById('questions-container'); DOM.questionCount = document.getElementById('question-count'); DOM.btnLoadSample = document.getElementById('btn-load-sample'); DOM.btnSubmitSurvey = document.getElementById('btn-submit-survey'); // Photo Upload DOM.photoUploadArea = document.getElementById('photo-upload-area'); DOM.photoInput = document.getElementById('photo-input'); DOM.btnBrowsePhotos = document.getElementById('btn-browse-photos'); DOM.photoPreviewGrid = document.getElementById('photo-preview-grid'); // Analysis Tab DOM.btnBackToSurveys = document.getElementById('btn-back-to-surveys'); DOM.analysisSurveyName = document.getElementById('analysis-survey-name'); DOM.analysisModel = document.getElementById('analysis-model'); DOM.btnGenerateAnalysis = document.getElementById('btn-generate-analysis'); DOM.analysisSurveyMeta = document.getElementById('analysis-survey-meta'); DOM.analysisQuestionSummary = document.getElementById('analysis-question-summary'); DOM.analysisOutput = document.getElementById('analysis-output'); DOM.analysisLoading = document.getElementById('analysis-loading'); DOM.exportButtons = document.getElementById('export-buttons'); DOM.btnExportText = document.getElementById('btn-export-text'); DOM.btnExportPdf = document.getElementById('btn-export-pdf'); DOM.btnEmail = document.getElementById('btn-email'); // Chat DOM.chatMessages = document.getElementById('chat-messages'); DOM.chatInput = document.getElementById('chat-input'); DOM.btnSendChat = document.getElementById('btn-send-chat'); // Modals DOM.deleteModal = document.getElementById('delete-modal'); DOM.btnCancelDelete = document.getElementById('btn-cancel-delete'); DOM.btnConfirmDelete = document.getElementById('btn-confirm-delete'); DOM.clearAllModal = document.getElementById('clear-all-modal'); DOM.btnCancelClearAll = document.getElementById('btn-cancel-clear-all'); DOM.btnConfirmClearAll = document.getElementById('btn-confirm-clear-all'); // Toast DOM.toast = document.getElementById('toast'); DOM.toastMessage = document.getElementById('toast-message'); } // ============================================ // API Functions // ============================================ async function fetchAPI(endpoint, options = {}) { const url = `${API_BASE_URL}${endpoint}`; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); } async function loadSurveyTemplate() { try { const data = await fetchAPI('/api/surveys/template'); AppState.surveyTemplate = data; renderQuestions(); } catch (error) { showToast('Failed to load survey template', 'error'); console.error('Error loading template:', error); } } async function loadSurveys() { try { const data = await fetchAPI('/api/surveys'); AppState.surveys = data.surveys; renderSurveysList(); } catch (error) { showToast('Failed to load surveys', 'error'); console.error('Error loading surveys:', error); } } async function createSurvey(surveyData) { try { const data = await fetchAPI('/api/surveys', { method: 'POST', body: JSON.stringify(surveyData) }); showToast('Survey created successfully', 'success'); return data.id; } catch (error) { showToast('Failed to create survey', 'error'); throw error; } } async function deleteSurvey(surveyId) { try { await fetchAPI(`/api/surveys/${surveyId}`, { method: 'DELETE' }); showToast('Survey deleted successfully', 'success'); } catch (error) { showToast('Failed to delete survey', 'error'); throw error; } } async function deleteAllSurveys() { try { await fetchAPI('/api/surveys', { method: 'DELETE' }); showToast('All surveys deleted', 'success'); } catch (error) { showToast('Failed to delete surveys', 'error'); throw error; } } async function getSurvey(surveyId) { try { return await fetchAPI(`/api/surveys/${surveyId}`); } catch (error) { showToast('Failed to load survey details', 'error'); throw error; } } async function exportSurvey(surveyId, format, content) { try { const data = await fetchAPI(`/api/surveys/${surveyId}/export`, { method: 'POST', body: JSON.stringify({ format, content }) }); return data; } catch (error) { showToast('Failed to export survey', 'error'); throw error; } } // ============================================ // UI Functions // ============================================ function switchTab(tabName) { // Update state AppState.currentTab = tabName; // Update tab buttons document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); // Update tab contents document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `tab-${tabName}`); }); // Special handling for analysis tab visibility if (tabName === 'analysis') { document.getElementById('tab-analysis').classList.add('active'); } // Update navigation document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); } function showToast(message, type = 'success') { DOM.toastMessage.textContent = message; DOM.toast.className = `toast ${type}`; DOM.toast.style.display = 'block'; setTimeout(() => { DOM.toast.style.display = 'none'; }, 3000); } function showModal(modal) { modal.style.display = 'flex'; } function hideModal(modal) { modal.style.display = 'none'; } // ============================================ // Survey List Functions // ============================================ function renderSurveysList() { if (AppState.surveys.length === 0) { DOM.surveysList.innerHTML = `

No surveys yet. Create your first survey!

`; return; } DOM.surveysList.innerHTML = AppState.surveys.map(survey => `

${escapeHtml(survey.name)}

${survey.has_analysis ? '📊 Analysis' : ''}
${escapeHtml(survey.client)} ${escapeHtml(survey.site)} ${formatDate(survey.created_at)}
`).join(''); // Add event listeners document.querySelectorAll('.survey-card').forEach(card => { card.addEventListener('click', (e) => { if (!e.target.closest('.btn-delete-survey')) { const surveyId = card.dataset.id; viewSurvey(surveyId); } }); }); document.querySelectorAll('.btn-delete-survey').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); AppState.deleteTargetId = btn.dataset.id; showModal(DOM.deleteModal); }); }); document.querySelectorAll('.btn-edit-survey').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const surveyId = btn.dataset.id; editSurvey(surveyId); }); }); } // ============================================ // Survey Form Functions // ============================================ function renderQuestions() { if (!AppState.surveyTemplate) return; const questions = AppState.surveyTemplate.questions; DOM.questionsContainer.innerHTML = questions.map((q, index) => { const inputId = `question-${q.id}`; let inputHtml = ''; if (q.type === 'textarea') { inputHtml = ``; } else if (q.type === 'select' && q.options) { const options = q.options.map(opt => `` ).join(''); inputHtml = ` `; } else { inputHtml = ``; } return `
Q${index + 1} ${escapeHtml(q.category)}
${escapeHtml(q.question)}
${inputHtml}
`; }).join(''); updateQuestionCount(); } function updateQuestionCount() { if (!AppState.surveyTemplate) return; DOM.questionCount.textContent = `(${AppState.surveyTemplate.questions.length} questions)`; } function loadSampleData() { // Fill metadata DOM.surveyName.value = SAMPLE_DATA.name; DOM.surveyClient.value = SAMPLE_DATA.client; DOM.surveySite.value = SAMPLE_DATA.site; DOM.surveyDescription.value = SAMPLE_DATA.description; DOM.surveyModel.value = SAMPLE_DATA.model; // Fill answers Object.entries(SAMPLE_DATA.answers).forEach(([qid, value]) => { const input = document.getElementById(`question-${qid}`); if (input) { input.value = value; } }); showToast('Sample data loaded', 'success'); } function getFormData() { const answers = {}; if (AppState.surveyTemplate) { AppState.surveyTemplate.questions.forEach(q => { const input = document.getElementById(`question-${q.id}`); if (input) { answers[q.id] = input.value.trim(); } }); } return { name: DOM.surveyName.value.trim(), client: DOM.surveyClient.value.trim(), site: DOM.surveySite.value.trim(), description: DOM.surveyDescription.value.trim(), model: DOM.surveyModel.value, answers: answers, photos: AppState.uploadedPhotos }; } async function handleFormSubmit(e) { e.preventDefault(); // Validation if (!DOM.surveyName.value.trim()) { showToast('Please enter a survey name', 'error'); DOM.surveyName.focus(); return; } if (!DOM.surveyClient.value.trim()) { showToast('Please enter a client name', 'error'); DOM.surveyClient.focus(); return; } if (!DOM.surveySite.value.trim()) { showToast('Please enter a site location', 'error'); DOM.surveySite.focus(); return; } const formData = getFormData(); try { if (AppState.currentSurveyId) { // Update existing survey await updateSurvey(AppState.currentSurveyId, formData); await loadSurveys(); resetForm(); switchTab('surveys'); } else { // Create new survey const surveyId = await createSurvey(formData); await loadSurveys(); resetForm(); switchTab('surveys'); } } catch (error) { console.error('Error saving survey:', error); } } function resetForm() { DOM.surveyForm.reset(); AppState.uploadedPhotos = []; AppState.currentSurveyId = null; renderPhotoPreviews(); // Reset button text const submitBtn = document.getElementById('btn-submit-survey'); if (submitBtn) { submitBtn.textContent = 'Create Survey'; } } // ============================================ // Photo Upload Functions // ============================================ function handlePhotoUpload(files) { Array.from(files).forEach(file => { if (!file.type.startsWith('image/')) return; const reader = new FileReader(); reader.onload = (e) => { AppState.uploadedPhotos.push({ name: file.name, data: e.target.result }); renderPhotoPreviews(); }; reader.readAsDataURL(file); }); } function renderPhotoPreviews() { if (AppState.uploadedPhotos.length === 0) { DOM.photoPreviewGrid.innerHTML = ''; return; } DOM.photoPreviewGrid.innerHTML = AppState.uploadedPhotos.map((photo, index) => `
${escapeHtml(photo.name)}
`).join(''); document.querySelectorAll('.photo-preview-remove').forEach(btn => { btn.addEventListener('click', () => { const index = parseInt(btn.dataset.index); AppState.uploadedPhotos.splice(index, 1); renderPhotoPreviews(); }); }); } // ============================================ // Analysis Functions // ============================================ async function viewSurvey(surveyId) { try { const survey = await getSurvey(surveyId); AppState.currentSurveyId = surveyId; // Populate analysis view DOM.analysisSurveyName.textContent = survey.name; DOM.analysisModel.value = survey.model; // Populate survey meta DOM.analysisSurveyMeta.innerHTML = `
Client ${escapeHtml(survey.client)}
Site ${escapeHtml(survey.site)}
Created ${formatDate(survey.created_at)}
Description ${escapeHtml(survey.description || 'No description')}
`; // Populate question summary by category const categories = {}; if (AppState.surveyTemplate) { AppState.surveyTemplate.questions.forEach(q => { const hasAnswer = survey.answers && survey.answers[q.id]; if (!categories[q.category]) { categories[q.category] = { total: 0, answered: 0 }; } categories[q.category].total++; if (hasAnswer) categories[q.category].answered++; }); } DOM.analysisQuestionSummary.innerHTML = Object.entries(categories).map(([cat, stats]) => `
${escapeHtml(cat)} ${stats.answered}/${stats.total}
`).join(''); // Check for saved analysis if (survey.analysis) { // Show saved analysis DOM.analysisOutput.innerHTML = '
' + formatMarkdown(survey.analysis) + '
'; AppState.analysisContent = survey.analysis; DOM.exportButtons.style.display = 'flex'; // Add delete analysis button DOM.analysisOutput.innerHTML += `
`; document.getElementById('btn-delete-analysis').addEventListener('click', deleteAnalysis); } else { // Show placeholder DOM.analysisOutput.innerHTML = `

Click "Generate Recommendations" to get AI-powered insights

`; DOM.exportButtons.style.display = 'none'; AppState.analysisContent = ''; } // Clear chat DOM.chatMessages.innerHTML = `

Ask me anything about this survey - budget planning, security recommendations, timeline suggestions, or technical details!

`; DOM.chatInput.disabled = false; DOM.btnSendChat.disabled = false; switchTab('analysis'); } catch (error) { console.error('Error viewing survey:', error); } } async function editSurvey(surveyId) { try { const survey = await getSurvey(surveyId); AppState.currentSurveyId = surveyId; // Populate form with existing data DOM.surveyName.value = survey.name || ''; DOM.surveyClient.value = survey.client || ''; DOM.surveySite.value = survey.site || ''; DOM.surveyDescription.value = survey.description || ''; // Set model if (DOM.surveyModel) { DOM.surveyModel.value = survey.model || ''; } // Populate answers if (survey.answers && AppState.surveyTemplate) { AppState.surveyTemplate.questions.forEach(q => { const inputId = `question-${q.id}`; const element = document.getElementById(inputId); if (element && survey.answers[q.id]) { element.value = survey.answers[q.id]; } }); } // Update button text const submitBtn = document.getElementById('btn-submit-survey'); if (submitBtn) { submitBtn.textContent = 'Update Survey'; } updateQuestionCount(); switchTab('create'); showToast('Survey loaded for editing', 'success'); } catch (error) { console.error('Error loading survey for edit:', error); showToast('Failed to load survey for editing', 'error'); } } async function updateSurvey(surveyId, surveyData) { try { const data = await fetchAPI(`/api/surveys/${surveyId}`, { method: 'PUT', body: JSON.stringify(surveyData) }); showToast('Survey updated successfully', 'success'); return data; } catch (error) { showToast('Failed to update survey', 'error'); throw error; } } async function generateAnalysis() { if (AppState.isGenerating || !AppState.currentSurveyId) return; AppState.isGenerating = true; DOM.analysisLoading.style.display = 'flex'; DOM.analysisOutput.innerHTML = ''; DOM.exportButtons.style.display = 'none'; DOM.btnGenerateAnalysis.disabled = true; try { const model = DOM.analysisModel.value; // Use streaming fetch const response = await fetch(`${API_BASE_URL}/api/surveys/${AppState.currentSurveyId}/analyze`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model }) }); if (!response.ok) throw new Error('Failed to generate analysis'); const reader = response.body.getReader(); const decoder = new TextDecoder(); let content = ''; // Create content container const contentDiv = document.createElement('div'); contentDiv.className = 'analysis-content'; DOM.analysisOutput.appendChild(contentDiv); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); content += chunk; contentDiv.innerHTML = formatMarkdown(content); // Auto-scroll to bottom DOM.analysisOutput.scrollTop = DOM.analysisOutput.scrollHeight; } AppState.analysisContent = content; DOM.exportButtons.style.display = 'flex'; // Automatically save analysis await saveAnalysis(AppState.currentSurveyId, content); showToast('Analysis complete and saved', 'success'); } catch (error) { console.error('Error generating analysis:', error); showToast('Failed to generate analysis', 'error'); DOM.analysisOutput.innerHTML = `

Failed to generate analysis. Please try again.

`; } finally { AppState.isGenerating = false; DOM.analysisLoading.style.display = 'none'; DOM.btnGenerateAnalysis.disabled = false; } } async function saveAnalysis(surveyId, analysisContent) { try { await fetchAPI(`/api/surveys/${surveyId}/analysis`, { method: 'POST', body: JSON.stringify({ analysis: analysisContent }) }); console.log('Analysis saved successfully'); } catch (error) { console.error('Error saving analysis:', error); } } async function deleteAnalysis() { if (!AppState.currentSurveyId) return; if (!confirm('Are you sure you want to delete this analysis? This action cannot be undone.')) { return; } try { await fetchAPI(`/api/surveys/${AppState.currentSurveyId}/analysis`, { method: 'DELETE' }); // Clear the analysis display AppState.analysisContent = ''; DOM.analysisOutput.innerHTML = `

Click "Generate Recommendations" to get AI-powered insights

`; DOM.exportButtons.style.display = 'none'; showToast('Analysis deleted', 'success'); } catch (error) { console.error('Error deleting analysis:', error); showToast('Failed to delete analysis', 'error'); } } function formatMarkdown(text) { // Simple markdown formatting return text .replace(/^### (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^# (.*$)/gim, '

$1

') .replace(/\*\*\*(.*?)\*\*\*/g, '$1') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/^\s*[-*+]\s+(.*$)/gim, '
  • $1
  • ') .replace(/(
  • .*<\/li>\n?)+/g, '') .replace(/^\d+\.\s+(.*$)/gim, '
  • $1
  • ') .replace(/---+/g, '
    ') .replace(/\n/g, '
    '); } async function sendChatMessage() { const message = DOM.chatInput.value.trim(); if (!message || !AppState.currentSurveyId) return; // Add user message addChatMessage('user', message); DOM.chatInput.value = ''; // Show loading DOM.btnSendChat.disabled = true; try { const response = await fetchAPI(`/api/surveys/${AppState.currentSurveyId}/chat`, { method: 'POST', body: JSON.stringify({ message, model: DOM.analysisModel.value }) }); addChatMessage('assistant', response.response); } catch (error) { showToast('Failed to get response', 'error'); addChatMessage('system', 'Sorry, I encountered an error. Please try again.'); } finally { DOM.btnSendChat.disabled = false; } } function addChatMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${role}`; messageDiv.innerHTML = `

    ${escapeHtml(content)}

    `; DOM.chatMessages.appendChild(messageDiv); DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; } // ============================================ // Export Functions // ============================================ async function exportToText() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'text', AppState.analysisContent); downloadFile(data.filename, data.content, 'text/plain'); showToast('Report exported as text', 'success'); } catch (error) { console.error('Export error:', error); } } async function exportToPDF() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'pdf', AppState.analysisContent); // Decode base64 PDF content const pdfContent = atob(data.content); const bytes = new Uint8Array(pdfContent.length); for (let i = 0; i < pdfContent.length; i++) { bytes[i] = pdfContent.charCodeAt(i); } // Create blob and download const blob = new Blob([bytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = data.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('PDF report downloaded', 'success'); } catch (error) { console.error('PDF Export error:', error); showToast('Failed to export PDF', 'error'); } } async function emailReport() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'email', AppState.analysisContent); // Open email client with mailto link if (data.mailto_link) { window.open(data.mailto_link, '_blank'); showToast('Email client opened', 'success'); } else { showToast('Failed to generate email link', 'error'); } } catch (error) { console.error('Email error:', error); showToast('Failed to open email', 'error'); } } function downloadFile(filename, content, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // ============================================ // Utility Functions // ============================================ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatDate(dateString) { if (!dateString) return 'Unknown'; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } // ============================================ // Event Listeners Setup // ============================================ function setupEventListeners() { // Tab navigation document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { switchTab(btn.dataset.tab); }); }); // Create survey form DOM.surveyForm.addEventListener('submit', handleFormSubmit); DOM.btnLoadSample.addEventListener('click', loadSampleData); // Photo upload DOM.btnBrowsePhotos.addEventListener('click', () => DOM.photoInput.click()); DOM.photoInput.addEventListener('change', (e) => handlePhotoUpload(e.target.files)); DOM.photoUploadArea.addEventListener('dragover', (e) => { e.preventDefault(); DOM.photoUploadArea.classList.add('dragover'); }); DOM.photoUploadArea.addEventListener('dragleave', () => { DOM.photoUploadArea.classList.remove('dragover'); }); DOM.photoUploadArea.addEventListener('drop', (e) => { e.preventDefault(); DOM.photoUploadArea.classList.remove('dragover'); handlePhotoUpload(e.dataTransfer.files); }); DOM.photoUploadArea.addEventListener('click', (e) => { if (e.target === DOM.photoUploadArea || e.target.closest('svg') || e.target.tagName === 'P') { DOM.photoInput.click(); } }); // Clear all surveys DOM.btnClearAll.addEventListener('click', () => { showModal(DOM.clearAllModal); }); DOM.btnCancelClearAll.addEventListener('click', () => { hideModal(DOM.clearAllModal); }); DOM.btnConfirmClearAll.addEventListener('click', async () => { hideModal(DOM.clearAllModal); try { await deleteAllSurveys(); await loadSurveys(); } catch (error) { console.error('Error clearing surveys:', error); } }); // Delete survey modal DOM.btnCancelDelete.addEventListener('click', () => { hideModal(DOM.deleteModal); AppState.deleteTargetId = null; }); DOM.btnConfirmDelete.addEventListener('click', async () => { if (AppState.deleteTargetId) { hideModal(DOM.deleteModal); try { await deleteSurvey(AppState.deleteTargetId); await loadSurveys(); } catch (error) { console.error('Error deleting survey:', error); } AppState.deleteTargetId = null; } }); // Analysis tab DOM.btnBackToSurveys.addEventListener('click', () => { switchTab('surveys'); }); DOM.btnGenerateAnalysis.addEventListener('click', generateAnalysis); // Chat DOM.btnSendChat.addEventListener('click', sendChatMessage); DOM.chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendChatMessage(); } }); // Export DOM.btnExportText.addEventListener('click', exportToText); DOM.btnExportPdf.addEventListener('click', exportToPDF); DOM.btnEmail.addEventListener('click', emailReport); } // ============================================ // Initialization // ============================================ // Load available AI models from API async function loadModels() { try { const data = await fetchAPI('/api/models'); if (data.models && data.models.length > 0) { const surveySelect = document.getElementById('survey-model'); const analysisSelect = document.getElementById('analysis-model'); // Clear loading option surveySelect.innerHTML = ''; analysisSelect.innerHTML = ''; // Add models to both dropdowns data.models.forEach(model => { const option = document.createElement('option'); option.value = model.id; option.textContent = model.name; surveySelect.appendChild(option.cloneNode(true)); analysisSelect.appendChild(option); }); // Set default if (data.default) { surveySelect.value = data.default; analysisSelect.value = data.default; } } } catch (error) { console.error('Failed to load models:', error); // Keep default options if API fails } } function init() { cacheDOMElements(); setupEventListeners(); loadModels(); // Load available AI models loadSurveyTemplate(); loadSurveys(); } // Start the app when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }