// Idea House JavaScript - Handles data field selection and Coze API processing // Global variables let dataFields = []; let filteredDataFields = []; let selectedFields = new Map(); // Map of field_id -> description // Filter state variables let columnFilters = { id: '', description: '', type: '', coverage: { min: null, max: null }, userCount: null, alphaCount: null }; let sortColumn = null; let sortOrder = 'asc'; // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', function() { // Set up event listeners document.getElementById('loadDataFieldsBtn').addEventListener('click', loadDataFields); document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection); document.getElementById('processFieldsBtn').addEventListener('click', processSelectedFields); // Set up API token toggle button const toggleBtn = document.getElementById('toggleApiTokenBtn'); const tokenInput = document.getElementById('cozeApiTokenInput'); toggleBtn.addEventListener('click', function() { if (tokenInput.type === 'password') { tokenInput.type = 'text'; toggleBtn.textContent = 'Hide'; } else { tokenInput.type = 'password'; toggleBtn.textContent = 'Show'; } }); // Load saved configuration loadSavedCozeConfig(); // Set up save configuration button document.getElementById('saveCozeConfigBtn').addEventListener('click', saveCozeConfig); // Set up clear configuration button document.getElementById('clearCozeConfigBtn').addEventListener('click', clearCozeConfig); // Set up filter event listeners setupFilterEventListeners(); }); // Load data fields from BRAIN API async function loadDataFields() { const region = document.getElementById('regionInput').value; const delay = document.getElementById('delayInput').value; const universe = document.getElementById('universeInput').value; const datasetId = document.getElementById('datasetInput').value; // Show loading state document.getElementById('dataFieldsStats').textContent = 'Loading data fields...'; document.getElementById('tableContainer').style.display = 'none'; try { // Get session ID from localStorage or cookie const sessionId = localStorage.getItem('brain_session_id'); if (!sessionId) { alert('Please connect to BRAIN first from the main page'); return; } // Call the proxy endpoint const params = new URLSearchParams({ region: region, delay: delay, universe: universe, dataset_id: datasetId }); // Log the API calls console.log('🚀 Making API calls to fetch data fields and dataset description'); console.log('📋 Parameters:', { region: region, delay: delay, universe: universe, dataset_id: datasetId }); // Fetch data fields and dataset description in parallel const [dataFieldsResponse, descriptionResponse] = await Promise.all([ fetch(`/idea-house/api/get-datafields-proxy?${params}`, { method: 'GET', headers: { 'Session-ID': sessionId } }), fetch(`/idea-house/api/get-dataset-description?${params}`, { method: 'GET', headers: { 'Session-ID': sessionId } }) ]); console.log('📥 Received responses:'); console.log(' Data fields status:', dataFieldsResponse.status); console.log(' Dataset description status:', descriptionResponse.status); if (!dataFieldsResponse.ok) { const errorData = await dataFieldsResponse.json(); throw new Error(errorData.error || 'Failed to fetch data fields'); } dataFields = await dataFieldsResponse.json(); // Get dataset description if available let datasetDescription = ''; if (descriptionResponse.ok) { console.log('✅ Dataset description response OK, parsing JSON...'); const descriptionData = await descriptionResponse.json(); console.log('📄 Description data:', descriptionData); if (descriptionData.success) { datasetDescription = descriptionData.description; // Store it globally for later use window.currentDatasetDescription = datasetDescription; console.log('✅ Dataset description stored:', datasetDescription); } else { console.log('⚠️ Description response success=false'); } } else { console.log('❌ Dataset description response not OK:', descriptionResponse.status); try { const errorData = await descriptionResponse.json(); console.log('❌ Error details:', errorData); } catch (e) { console.log('❌ Could not parse error response'); } } // Update stats document.getElementById('dataFieldsStats').textContent = `Loaded ${dataFields.length} data fields`; // Populate table with dataset description populateDataFieldsTable(datasetDescription); // Show table document.getElementById('tableContainer').style.display = 'block'; } catch (error) { console.error('Failed to load data fields:', error); document.getElementById('dataFieldsStats').textContent = `Error: ${error.message}`; alert(`Failed to load data fields: ${error.message}`); } } // Populate the data fields table with filtering and sorting function populateDataFieldsTable(datasetDescription) { console.log('📊 populateDataFieldsTable called with description:', datasetDescription); const tableBody = document.getElementById('dataFieldsTableBody'); const highCoverageFilter = document.getElementById('filterHighCoverage')?.checked || false; const popularFilter = document.getElementById('filterPopular')?.checked || false; const matrixOnlyFilter = document.getElementById('filterMatrixOnly')?.checked || false; // Display dataset description if available if (datasetDescription) { console.log('🎯 Displaying passed dataset description'); displayDatasetDescription(datasetDescription); } else if (window.currentDatasetDescription) { console.log('🎯 Displaying stored dataset description'); displayDatasetDescription(window.currentDatasetDescription); } else { console.log('⚠️ No dataset description available'); } // Apply filters filteredDataFields = dataFields.filter(field => { // Column-specific filters // ID filter if (columnFilters.id && !field.id.toLowerCase().includes(columnFilters.id.toLowerCase())) { return false; } // Description filter if (columnFilters.description && !field.description.toLowerCase().includes(columnFilters.description.toLowerCase())) { return false; } // Type filter if (columnFilters.type && field.type !== columnFilters.type) { return false; } // Coverage range filter if (columnFilters.coverage.min !== null && field.coverage * 100 < columnFilters.coverage.min) { return false; } if (columnFilters.coverage.max !== null && field.coverage * 100 > columnFilters.coverage.max) { return false; } // User count filter if (columnFilters.userCount !== null && field.userCount < columnFilters.userCount) { return false; } // Alpha count filter if (columnFilters.alphaCount !== null && field.alphaCount < columnFilters.alphaCount) { return false; } // High coverage filter if (highCoverageFilter && field.coverage < 0.9) { return false; } // Popular filter if (popularFilter && field.userCount < 1000) { return false; } // Matrix type filter if (matrixOnlyFilter && field.type !== 'MATRIX') { return false; } return true; }); // Sort filtered data fields if (sortColumn) { filteredDataFields.sort((a, b) => { let aVal = a[sortColumn]; let bVal = b[sortColumn]; // Handle numeric values if (sortColumn === 'coverage' || sortColumn === 'userCount' || sortColumn === 'alphaCount') { aVal = Number(aVal); bVal = Number(bVal); } else { // String comparison aVal = String(aVal).toLowerCase(); bVal = String(bVal).toLowerCase(); } if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); } // Clear table tableBody.innerHTML = ''; if (filteredDataFields.length === 0) { tableBody.innerHTML = 'No data fields found matching the filters'; updateDataFieldsStats(); return; } // Create table rows filteredDataFields.forEach(field => { const row = document.createElement('tr'); row.dataset.fieldId = field.id; row.dataset.fieldDescription = field.description; if (selectedFields.has(field.id)) { row.classList.add('selected'); } row.innerHTML = ` ${field.id} ${field.description} ${field.type || 'N/A'} ${field.coverage ? (field.coverage * 100).toFixed(1) + '%' : 'N/A'} ${field.userCount ? field.userCount.toLocaleString() : 'N/A'} ${field.alphaCount ? field.alphaCount.toLocaleString() : 'N/A'} `; // Add click handler for row row.addEventListener('click', function(e) { if (e.target.type !== 'checkbox') { const checkbox = row.querySelector('.field-checkbox'); checkbox.checked = !checkbox.checked; handleFieldSelection(checkbox); } }); // Add change handler for checkbox const checkbox = row.querySelector('.field-checkbox'); checkbox.addEventListener('change', function() { handleFieldSelection(this); }); tableBody.appendChild(row); }); // Update stats and populate type filter updateDataFieldsStats(); populateTypeFilter(); updateSelectAllCheckbox(); } // Handle field selection function handleFieldSelection(checkbox) { const fieldId = checkbox.dataset.fieldId; const fieldDescription = checkbox.dataset.fieldDescription; const row = checkbox.closest('tr'); if (checkbox.checked) { selectedFields.set(fieldId, fieldDescription); row.classList.add('selected'); } else { selectedFields.delete(fieldId); row.classList.remove('selected'); } updateSelectedFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } // Update the display of selected fields function updateSelectedFieldsDisplay() { const selectedFieldsList = document.getElementById('selectedFieldsList'); const selectedFieldsSection = document.getElementById('selectedFieldsSection'); if (selectedFields.size === 0) { selectedFieldsSection.style.display = 'none'; return; } selectedFieldsSection.style.display = 'block'; selectedFieldsList.innerHTML = ''; selectedFields.forEach((description, fieldId) => { const fieldItem = document.createElement('div'); fieldItem.className = 'selected-field-item'; fieldItem.textContent = `${fieldId}: ${description}`; selectedFieldsList.appendChild(fieldItem); }); } // Clear all selections function clearSelection() { selectedFields.clear(); // Uncheck all checkboxes and remove selected class document.querySelectorAll('.field-checkbox').forEach(checkbox => { checkbox.checked = false; checkbox.closest('tr').classList.remove('selected'); }); updateSelectedFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } // Process selected fields using Coze API async function processSelectedFields() { if (selectedFields.size === 0) { alert('Please select at least one field'); return; } // Show loading overlay with Coze API specific message const loadingOverlay = document.getElementById('loadingOverlay'); const loadingContent = loadingOverlay.querySelector('.loading-content'); loadingContent.innerHTML = `

🚀 Sending Request to Coze API...

Processing ${selectedFields.size} selected fields through Coze workflow

📡 Connecting to Coze API...
⚙️ Running workflow analysis...
📊 Generating insights...

`; loadingOverlay.style.display = 'flex'; try { // Prepare the data in the required format {"id":"description"} const selectedFieldsObject = {}; selectedFields.forEach((description, fieldId) => { selectedFieldsObject[fieldId] = description; }); // Get Coze API configuration const cozeApiToken = document.getElementById('cozeApiTokenInput').value; const workflowId = document.getElementById('workflowIdInput').value; // Validate inputs if (!cozeApiToken) { alert('Please enter a Coze API token'); document.getElementById('loadingOverlay').style.display = 'none'; return; } if (!workflowId) { alert('Please enter a Workflow ID'); document.getElementById('loadingOverlay').style.display = 'none'; return; } // Update loading message to show API call is happening loadingContent.innerHTML = `

📡 Coze API Request in Progress...

Workflow ID: ${workflowId}

Selected Fields: ${Object.keys(selectedFieldsObject).join(', ')}

✅ API credentials validated
🔄 Sending request to Coze servers...
⏳ Please wait for response...

`; console.log('🚀 Starting Coze API request...'); console.log('📋 Selected fields:', selectedFieldsObject); console.log('🔑 Using API token ending with:', cozeApiToken.slice(-10)); console.log('⚙️ Workflow ID:', workflowId); // Call the process endpoint const response = await fetch('/idea-house/api/process-fields', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selected_fields: selectedFieldsObject, coze_api_token: cozeApiToken, workflow_id: workflowId, dataset_description: window.currentDatasetDescription || '' }) }); console.log('📡 Received response from server'); const result = await response.json(); if (!response.ok) { console.error('❌ Coze API request failed:', result.error); throw new Error(result.error || 'Failed to process fields'); } console.log('✅ Coze API request successful!'); console.log('📊 Result:', result); // Update loading message to show success loadingContent.innerHTML = `

✅ Coze API Response Received!

Successfully processed through workflow

📥 Response received from Coze
🎉 Formatting results...

`; // Small delay to show the success message await new Promise(resolve => setTimeout(resolve, 1000)); // Display results displayResults(result); } catch (error) { console.error('💥 Failed to process fields via Coze API:', error); alert(`Failed to process fields via Coze API: ${error.message}`); } finally { // Hide loading overlay document.getElementById('loadingOverlay').style.display = 'none'; // Reset loading content for next time loadingContent.innerHTML = `

Processing...

Please wait while we analyze your selected fields...

`; } } // Display results in markdown format function displayResults(result) { const resultsSection = document.getElementById('resultsSection'); const resultsContent = document.getElementById('resultsContent'); // Show results section resultsSection.style.display = 'block'; // Format the results as markdown - simplified version let markdown = '# Analysis Results\n\n'; // Add selected fields section markdown += '## Selected Fields\n\n'; Object.entries(result.selected_fields).forEach(([fieldId, description]) => { markdown += `- **${fieldId}**: ${description}\n`; }); markdown += '\n'; // Add output section - only show the actual analysis output markdown += '## Analysis Output\n\n'; if (result.output) { // If the output is already formatted text, use it directly if (typeof result.output === 'string') { markdown += result.output; } else { // If it's an object, try to display it nicely markdown += '```json\n'; markdown += JSON.stringify(result.output, null, 2); markdown += '\n```\n'; } } else { markdown += '_No output data available_'; } // Render the markdown as HTML resultsContent.innerHTML = renderMarkdown(markdown); // Scroll to results resultsSection.scrollIntoView({ behavior: 'smooth' }); } // Helper function to format markdown (optional enhancement) function renderMarkdown(markdown) { // This is an improved markdown renderer that handles lists better let html = markdown; // First, escape HTML to prevent XSS html = html.replace(/&/g, '&') .replace(//g, '>'); // Code blocks (must be before inline code) html = html.replace(/```([\s\S]*?)```/g, function(match, code) { return '
' + code.trim() + '
'; }); // Headers html = html.replace(/^#### (.*$)/gim, '

$1

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

$1

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

$1

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

$1

'); // Bold (must be before italic) html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // Italic html = html.replace(/\*([^*\n]+)\*/g, '$1'); // Inline code html = html.replace(/`([^`]+)`/g, '$1'); // Handle lists more carefully // Split into lines for better list processing const lines = html.split('\n'); let inList = false; let processedLines = []; for (let i = 0; i < lines.length; i++) { let line = lines[i]; // Check if line is a list item if (line.match(/^[\*\-\+] /)) { // Replace list marker with proper HTML line = line.replace(/^[\*\-\+] (.*)$/, '
  • $1
  • '); // If not in a list, start one if (!inList) { processedLines.push(''); processedLines.push('
      '); inList = true; } processedLines.push(line); } else { // Not a list item if (inList) { // Close the list if (i > 0 && lines[i-1].match(/^\d+\. /)) { processedLines.push('
    '); } else { processedLines.push(''); } inList = false; } processedLines.push(line); } } // Close any remaining list if (inList) { if (lines[lines.length - 1].match(/^\d+\. /)) { processedLines.push(''); } else { processedLines.push(''); } } html = processedLines.join('\n'); // Line breaks - convert double newlines to paragraphs html = html.replace(/\n\n/g, '

    '); html = '

    ' + html + '

    '; // Clean up empty paragraphs html = html.replace(/

    \s*<\/p>/g, ''); html = html.replace(/

    ()/g, '$1'); html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1'); html = html.replace(/

    (