import './style.css' import { api } from './api/client' // Global state // Initialize app document.addEventListener('DOMContentLoaded', () => { initializeQueryInput(); initializeFileUpload(); initializeModal(); loadDatabaseSchema(); }); // Query Input Functionality function initializeQueryInput() { const queryInput = document.getElementById('query-input') as HTMLTextAreaElement; const queryButton = document.getElementById('query-button') as HTMLButtonElement; queryButton.addEventListener('click', async () => { const query = queryInput.value.trim(); if (!query) return; queryButton.disabled = true; queryButton.innerHTML = ''; try { const response = await api.processQuery({ query, llm_provider: 'openai' // Default to OpenAI }); displayResults(response, query); // Clear the input field on success queryInput.value = ''; } catch (error) { displayError(error instanceof Error ? error.message : 'Query failed'); } finally { queryButton.disabled = false; queryButton.textContent = 'Query'; } }); // Allow Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux) to submit queryInput.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { queryButton.click(); } }); } // File Upload Functionality function initializeFileUpload() { const dropZone = document.getElementById('drop-zone') as HTMLDivElement; const fileInput = document.getElementById('file-input') as HTMLInputElement; const browseButton = document.getElementById('browse-button') as HTMLButtonElement; // Browse button click browseButton.addEventListener('click', () => fileInput.click()); // File input change fileInput.addEventListener('change', (e) => { const files = (e.target as HTMLInputElement).files; if (files && files.length > 0) { handleFileUpload(files[0]); } }); // Drag and drop dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('dragover'); }); dropZone.addEventListener('drop', async (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); const files = e.dataTransfer?.files; if (files && files.length > 0) { handleFileUpload(files[0]); } }); } // Handle file upload async function handleFileUpload(file: File) { try { const response = await api.uploadFile(file); if (response.error) { displayError(response.error); } else { displayUploadSuccess(response); await loadDatabaseSchema(); } } catch (error) { displayError(error instanceof Error ? error.message : 'Upload failed'); } } // Load database schema async function loadDatabaseSchema() { try { const response = await api.getSchema(); if (!response.error) { displayTables(response.tables); } } catch (error) { console.error('Failed to load schema:', error); } } // Display query results function displayResults(response: QueryResponse, query: string) { const resultsSection = document.getElementById('results-section') as HTMLElement; const sqlDisplay = document.getElementById('sql-display') as HTMLDivElement; const resultsContainer = document.getElementById('results-container') as HTMLDivElement; resultsSection.style.display = 'block'; // Display natural language query and SQL sqlDisplay.innerHTML = `
${response.sql}
No results found.
'; } else { const table = createResultsTable(response.results, response.columns); resultsContainer.innerHTML = ''; resultsContainer.appendChild(table); } // Initialize toggle button const toggleButton = document.getElementById('toggle-results') as HTMLButtonElement; toggleButton.addEventListener('click', () => { resultsContainer.style.display = resultsContainer.style.display === 'none' ? 'block' : 'none'; toggleButton.textContent = resultsContainer.style.display === 'none' ? 'Show' : 'Hide'; }); } // Create results table function createResultsTable(results: RecordNo tables loaded. Upload data or use sample data to get started.
'; return; } tablesList.innerHTML = ''; tables.forEach(table => { const tableItem = document.createElement('div'); tableItem.className = 'table-item'; // Header section const tableHeader = document.createElement('div'); tableHeader.className = 'table-header'; const tableLeft = document.createElement('div'); tableLeft.style.display = 'flex'; tableLeft.style.alignItems = 'center'; tableLeft.style.gap = '1rem'; const tableName = document.createElement('div'); tableName.className = 'table-name'; tableName.textContent = table.name; const tableInfo = document.createElement('div'); tableInfo.className = 'table-info'; tableInfo.textContent = `${table.row_count} rows, ${table.columns.length} columns`; tableLeft.appendChild(tableName); tableLeft.appendChild(tableInfo); const removeButton = document.createElement('button'); removeButton.className = 'remove-table-button'; removeButton.innerHTML = '×'; removeButton.title = 'Remove table'; removeButton.onclick = () => removeTable(table.name); tableHeader.appendChild(tableLeft); tableHeader.appendChild(removeButton); // Columns section const tableColumns = document.createElement('div'); tableColumns.className = 'table-columns'; table.columns.forEach(column => { const columnTag = document.createElement('span'); columnTag.className = 'column-tag'; const columnName = document.createElement('span'); columnName.className = 'column-name'; columnName.textContent = column.name; const columnType = document.createElement('span'); columnType.className = 'column-type'; const typeEmoji = getTypeEmoji(column.type); columnType.textContent = `${typeEmoji} ${column.type}`; columnTag.appendChild(columnName); columnTag.appendChild(columnType); tableColumns.appendChild(columnTag); }); tableItem.appendChild(tableHeader); tableItem.appendChild(tableColumns); tablesList.appendChild(tableItem); }); } // Display upload success function displayUploadSuccess(response: FileUploadResponse) { // Close modal const modal = document.getElementById('upload-modal') as HTMLElement; modal.style.display = 'none'; // Show success message const successDiv = document.createElement('div'); successDiv.className = 'success-message'; successDiv.textContent = `Table "${response.table_name}" created successfully with ${response.row_count} rows!`; successDiv.style.cssText = ` background: rgba(40, 167, 69, 0.1); border: 1px solid var(--success-color); color: var(--success-color); padding: 1rem; border-radius: 8px; margin-bottom: 1rem; `; const tablesSection = document.getElementById('tables-section') as HTMLElement; tablesSection.insertBefore(successDiv, tablesSection.firstChild); // Remove success message after 3 seconds setTimeout(() => { successDiv.remove(); }, 3000); } // Display error function displayError(message: string) { const errorDiv = document.createElement('div'); errorDiv.className = 'error-message'; errorDiv.textContent = message; const resultsContainer = document.getElementById('results-container') as HTMLDivElement; resultsContainer.innerHTML = ''; resultsContainer.appendChild(errorDiv); const resultsSection = document.getElementById('results-section') as HTMLElement; resultsSection.style.display = 'block'; } // Initialize modal function initializeModal() { const uploadButton = document.getElementById('upload-data-button') as HTMLButtonElement; const modal = document.getElementById('upload-modal') as HTMLElement; const closeButton = modal.querySelector('.close-modal') as HTMLButtonElement; // Open modal uploadButton.addEventListener('click', () => { modal.style.display = 'flex'; }); // Close modal closeButton.addEventListener('click', () => { modal.style.display = 'none'; }); // Close on background click modal.addEventListener('click', (e) => { if (e.target === modal) { modal.style.display = 'none'; } }); // Initialize sample data buttons const sampleButtons = modal.querySelectorAll('.sample-button'); sampleButtons.forEach(button => { button.addEventListener('click', async (e) => { const sampleType = (e.currentTarget as HTMLElement).dataset.sample; await loadSampleData(sampleType!); }); }); } // Remove table async function removeTable(tableName: string) { if (!confirm(`Are you sure you want to remove the table "${tableName}"?`)) { return; } try { const response = await fetch(`/api/table/${tableName}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('Failed to remove table'); } // Reload schema await loadDatabaseSchema(); // Show success message const successDiv = document.createElement('div'); successDiv.className = 'success-message'; successDiv.textContent = `Table "${tableName}" removed successfully!`; successDiv.style.cssText = ` background: rgba(40, 167, 69, 0.1); border: 1px solid var(--success-color); color: var(--success-color); padding: 1rem; border-radius: 8px; margin-bottom: 1rem; `; const tablesSection = document.getElementById('tables-section') as HTMLElement; tablesSection.insertBefore(successDiv, tablesSection.firstChild); setTimeout(() => { successDiv.remove(); }, 3000); } catch (error) { displayError(error instanceof Error ? error.message : 'Failed to remove table'); } } // Get emoji for data type function getTypeEmoji(type: string): string { const upperType = type.toUpperCase(); // SQLite types if (upperType.includes('INT')) return '🔢'; if (upperType.includes('REAL') || upperType.includes('FLOAT') || upperType.includes('DOUBLE')) return '💯'; if (upperType.includes('TEXT') || upperType.includes('CHAR') || upperType.includes('STRING')) return '📝'; if (upperType.includes('DATE') || upperType.includes('TIME')) return '📅'; if (upperType.includes('BOOL')) return '✓'; if (upperType.includes('BLOB')) return '📦'; // Default return '📊'; } // Load sample data async function loadSampleData(sampleType: string) { try { let filename: string; if (sampleType === 'users') { filename = 'users.json'; } else if (sampleType === 'products') { filename = 'products.csv'; } else if (sampleType === 'events') { filename = 'events.jsonl'; } else { throw new Error(`Unknown sample type: ${sampleType}`); } const response = await fetch(`/sample-data/${filename}`); if (!response.ok) { throw new Error('Failed to load sample data'); } const blob = await response.blob(); const file = new File([blob], filename, { type: blob.type }); // Upload the file await handleFileUpload(file); } catch (error) { displayError(error instanceof Error ? error.message : 'Failed to load sample data'); } }