|
|
// 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 = '<tr><td colspan="7" style="text-align: center; color: #666; padding: 40px;">No data fields found matching the filters</td></tr>';
|
|
|
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 = `
|
|
|
<td>
|
|
|
<input type="checkbox" class="field-checkbox" data-field-id="${field.id}" data-field-description="${field.description}" ${selectedFields.has(field.id) ? 'checked' : ''}>
|
|
|
</td>
|
|
|
<td><span class="data-field-id">${field.id}</span></td>
|
|
|
<td><span class="data-field-description">${field.description}</span></td>
|
|
|
<td><span class="data-field-type">${field.type || 'N/A'}</span></td>
|
|
|
<td><span class="data-field-coverage">${field.coverage ? (field.coverage * 100).toFixed(1) + '%' : 'N/A'}</span></td>
|
|
|
<td><span class="data-field-count">${field.userCount ? field.userCount.toLocaleString() : 'N/A'}</span></td>
|
|
|
<td><span class="data-field-count">${field.alphaCount ? field.alphaCount.toLocaleString() : 'N/A'}</span></td>
|
|
|
`;
|
|
|
|
|
|
// 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 = `
|
|
|
<h3>🚀 Sending Request to Coze API...</h3>
|
|
|
<p>Processing ${selectedFields.size} selected fields through Coze workflow</p>
|
|
|
<p style="font-size: 14px; color: #666; margin-top: 10px;">
|
|
|
📡 Connecting to Coze API...<br>
|
|
|
⚙️ Running workflow analysis...<br>
|
|
|
📊 Generating insights...
|
|
|
</p>
|
|
|
`;
|
|
|
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 = `
|
|
|
<h3>📡 Coze API Request in Progress...</h3>
|
|
|
<p>Workflow ID: ${workflowId}</p>
|
|
|
<p>Selected Fields: ${Object.keys(selectedFieldsObject).join(', ')}</p>
|
|
|
<p style="font-size: 14px; color: #4caf50; margin-top: 10px;">
|
|
|
✅ API credentials validated<br>
|
|
|
🔄 Sending request to Coze servers...<br>
|
|
|
⏳ Please wait for response...
|
|
|
</p>
|
|
|
`;
|
|
|
|
|
|
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 = `
|
|
|
<h3>✅ Coze API Response Received!</h3>
|
|
|
<p style="color: #4caf50;">Successfully processed through workflow</p>
|
|
|
<p style="font-size: 14px; margin-top: 10px;">
|
|
|
📥 Response received from Coze<br>
|
|
|
🎉 Formatting results...
|
|
|
</p>
|
|
|
`;
|
|
|
|
|
|
// 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 = `
|
|
|
<h3>Processing...</h3>
|
|
|
<p>Please wait while we analyze your selected fields...</p>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 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, '<')
|
|
|
.replace(/>/g, '>');
|
|
|
|
|
|
// Code blocks (must be before inline code)
|
|
|
html = html.replace(/```([\s\S]*?)```/g, function(match, code) {
|
|
|
return '<pre><code>' + code.trim() + '</code></pre>';
|
|
|
});
|
|
|
|
|
|
// Headers
|
|
|
html = html.replace(/^#### (.*$)/gim, '<h4>$1</h4>');
|
|
|
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
|
|
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
|
|
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
|
|
|
|
|
// Bold (must be before italic)
|
|
|
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
|
|
|
|
// Italic
|
|
|
html = html.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
|
|
|
|
|
|
// Inline code
|
|
|
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
|
|
|
|
// 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(/^[\*\-\+] (.*)$/, '<li>$1</li>');
|
|
|
|
|
|
// If not in a list, start one
|
|
|
if (!inList) {
|
|
|
processedLines.push('<ul>');
|
|
|
inList = true;
|
|
|
}
|
|
|
processedLines.push(line);
|
|
|
} else if (line.match(/^\d+\. /)) {
|
|
|
// Ordered list
|
|
|
line = line.replace(/^\d+\. (.*)$/, '<li>$1</li>');
|
|
|
|
|
|
// If not in a list or previous was unordered, start ordered list
|
|
|
if (!inList || (i > 0 && lines[i-1].match(/^[\*\-\+] /))) {
|
|
|
if (inList) processedLines.push('</ul>');
|
|
|
processedLines.push('<ol>');
|
|
|
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('</ol>');
|
|
|
} else {
|
|
|
processedLines.push('</ul>');
|
|
|
}
|
|
|
inList = false;
|
|
|
}
|
|
|
processedLines.push(line);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Close any remaining list
|
|
|
if (inList) {
|
|
|
if (lines[lines.length - 1].match(/^\d+\. /)) {
|
|
|
processedLines.push('</ol>');
|
|
|
} else {
|
|
|
processedLines.push('</ul>');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
html = processedLines.join('\n');
|
|
|
|
|
|
// Line breaks - convert double newlines to paragraphs
|
|
|
html = html.replace(/\n\n/g, '</p><p>');
|
|
|
html = '<p>' + html + '</p>';
|
|
|
|
|
|
// Clean up empty paragraphs
|
|
|
html = html.replace(/<p>\s*<\/p>/g, '');
|
|
|
html = html.replace(/<p>(<h[1-6]>)/g, '$1');
|
|
|
html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
|
|
|
html = html.replace(/<p>(<ul>|<ol>|<pre>)/g, '$1');
|
|
|
html = html.replace(/(<\/ul>|<\/ol>|<\/pre>)<\/p>/g, '$1');
|
|
|
|
|
|
// Single line breaks within paragraphs
|
|
|
html = html.replace(/\n/g, '<br>');
|
|
|
|
|
|
return html;
|
|
|
}
|
|
|
|
|
|
// Save Coze configuration to localStorage
|
|
|
function saveCozeConfig() {
|
|
|
const cozeApiToken = document.getElementById('cozeApiTokenInput').value;
|
|
|
const workflowId = document.getElementById('workflowIdInput').value;
|
|
|
|
|
|
// Save to localStorage
|
|
|
localStorage.setItem('coze_api_token', cozeApiToken);
|
|
|
localStorage.setItem('coze_workflow_id', workflowId);
|
|
|
|
|
|
// Show success message
|
|
|
const messageElement = document.getElementById('saveConfigMessage');
|
|
|
messageElement.style.display = 'inline';
|
|
|
|
|
|
// Hide message after 3 seconds
|
|
|
setTimeout(() => {
|
|
|
messageElement.style.display = 'none';
|
|
|
}, 3000);
|
|
|
}
|
|
|
|
|
|
// Load saved Coze configuration from localStorage
|
|
|
function loadSavedCozeConfig() {
|
|
|
const savedToken = localStorage.getItem('coze_api_token');
|
|
|
const savedWorkflowId = localStorage.getItem('coze_workflow_id');
|
|
|
|
|
|
if (savedToken) {
|
|
|
document.getElementById('cozeApiTokenInput').value = savedToken;
|
|
|
}
|
|
|
|
|
|
if (savedWorkflowId) {
|
|
|
document.getElementById('workflowIdInput').value = savedWorkflowId;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Clear Coze configuration and reset to defaults
|
|
|
function clearCozeConfig() {
|
|
|
// Remove from localStorage
|
|
|
localStorage.removeItem('coze_api_token');
|
|
|
localStorage.removeItem('coze_workflow_id');
|
|
|
|
|
|
// Reset to default values
|
|
|
document.getElementById('cozeApiTokenInput').value = 'pat_OCxUpnmL7hCvUxEWwcKL5XwUOdoiA3eWLzwY6L8W9sQVN1saJnoMrDNyhFhEn63l';
|
|
|
document.getElementById('workflowIdInput').value = '7522912027267956786';
|
|
|
|
|
|
// Show message
|
|
|
const messageElement = document.getElementById('saveConfigMessage');
|
|
|
messageElement.textContent = 'Configuration reset to default!';
|
|
|
messageElement.style.color = '#ff9800';
|
|
|
messageElement.style.display = 'inline';
|
|
|
|
|
|
// Hide message after 3 seconds
|
|
|
setTimeout(() => {
|
|
|
messageElement.style.display = 'none';
|
|
|
messageElement.textContent = 'Configuration saved!';
|
|
|
messageElement.style.color = '#4caf50';
|
|
|
}, 3000);
|
|
|
}
|
|
|
|
|
|
// Set up filter event listeners
|
|
|
function setupFilterEventListeners() {
|
|
|
// Quick filter checkboxes
|
|
|
const highCoverageFilter = document.getElementById('filterHighCoverage');
|
|
|
const popularFilter = document.getElementById('filterPopular');
|
|
|
const matrixOnlyFilter = document.getElementById('filterMatrixOnly');
|
|
|
|
|
|
// Filter action buttons
|
|
|
const selectAllBtn = document.getElementById('selectAllFiltered');
|
|
|
const clearAllBtn = document.getElementById('clearAllSelected');
|
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
|
|
|
|
// Set up quick filter listeners
|
|
|
if (highCoverageFilter) highCoverageFilter.onchange = () => populateDataFieldsTable();
|
|
|
if (popularFilter) popularFilter.onchange = () => populateDataFieldsTable();
|
|
|
if (matrixOnlyFilter) matrixOnlyFilter.onchange = () => populateDataFieldsTable();
|
|
|
|
|
|
// Set up action button listeners
|
|
|
if (selectAllBtn) selectAllBtn.onclick = selectAllFilteredFields;
|
|
|
if (clearAllBtn) clearAllBtn.onclick = clearAllSelectedFields;
|
|
|
|
|
|
if (selectAllCheckbox) {
|
|
|
selectAllCheckbox.onclick = (e) => {
|
|
|
e.stopPropagation();
|
|
|
if (selectAllCheckbox.checked) {
|
|
|
selectAllFilteredFields();
|
|
|
} else {
|
|
|
clearAllFilteredFields();
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Column filter listeners
|
|
|
document.querySelectorAll('.column-filter').forEach(filter => {
|
|
|
filter.addEventListener('input', (e) => {
|
|
|
const column = e.target.dataset.column;
|
|
|
const value = e.target.value;
|
|
|
|
|
|
if (column === 'userCount' || column === 'alphaCount') {
|
|
|
columnFilters[column] = value ? parseInt(value) : null;
|
|
|
} else {
|
|
|
columnFilters[column] = value;
|
|
|
}
|
|
|
|
|
|
// Add/remove active class
|
|
|
if (value) {
|
|
|
e.target.classList.add('active');
|
|
|
} else {
|
|
|
e.target.classList.remove('active');
|
|
|
}
|
|
|
|
|
|
populateDataFieldsTable();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// Coverage range filters
|
|
|
document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => {
|
|
|
filter.addEventListener('input', (e) => {
|
|
|
const isMin = e.target.classList.contains('column-filter-min');
|
|
|
const value = e.target.value;
|
|
|
|
|
|
if (isMin) {
|
|
|
columnFilters.coverage.min = value ? parseFloat(value) : null;
|
|
|
} else {
|
|
|
columnFilters.coverage.max = value ? parseFloat(value) : null;
|
|
|
}
|
|
|
|
|
|
// Add/remove active class
|
|
|
const minInput = e.target.parentElement.querySelector('.column-filter-min');
|
|
|
const maxInput = e.target.parentElement.querySelector('.column-filter-max');
|
|
|
|
|
|
if (minInput.value || maxInput.value) {
|
|
|
minInput.classList.add('active');
|
|
|
maxInput.classList.add('active');
|
|
|
} else {
|
|
|
minInput.classList.remove('active');
|
|
|
maxInput.classList.remove('active');
|
|
|
}
|
|
|
|
|
|
populateDataFieldsTable();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// Sort button listeners
|
|
|
document.querySelectorAll('.sort-btn').forEach(btn => {
|
|
|
btn.addEventListener('click', (e) => {
|
|
|
const column = e.target.dataset.column;
|
|
|
|
|
|
// Reset all other sort buttons
|
|
|
document.querySelectorAll('.sort-btn').forEach(b => {
|
|
|
if (b !== e.target) {
|
|
|
b.classList.remove('asc', 'desc');
|
|
|
b.dataset.order = 'asc';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// Toggle sort order
|
|
|
if (sortColumn === column) {
|
|
|
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
|
|
} else {
|
|
|
sortColumn = column;
|
|
|
sortOrder = 'asc';
|
|
|
}
|
|
|
|
|
|
e.target.dataset.order = sortOrder;
|
|
|
e.target.classList.remove('asc', 'desc');
|
|
|
e.target.classList.add(sortOrder);
|
|
|
|
|
|
populateDataFieldsTable();
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// Update data fields statistics
|
|
|
function updateDataFieldsStats() {
|
|
|
const dataFieldsCountEl = document.getElementById('dataFieldsCount');
|
|
|
const filteredCountEl = document.getElementById('filteredCount');
|
|
|
const selectedCountEl = document.getElementById('selectedFieldsCount');
|
|
|
|
|
|
if (dataFieldsCountEl) dataFieldsCountEl.textContent = `${dataFields.length} fields loaded`;
|
|
|
if (filteredCountEl) filteredCountEl.textContent = `${filteredDataFields.length} filtered`;
|
|
|
if (selectedCountEl) selectedCountEl.textContent = `${selectedFields.size} selected`;
|
|
|
}
|
|
|
|
|
|
// Populate type filter dropdown
|
|
|
function populateTypeFilter() {
|
|
|
const typeFilter = document.getElementById('typeFilter');
|
|
|
if (!typeFilter) return;
|
|
|
|
|
|
// Get unique types from current data fields
|
|
|
const uniqueTypes = [...new Set(dataFields.map(field => field.type))].sort();
|
|
|
|
|
|
// Clear existing options except "All Types"
|
|
|
typeFilter.innerHTML = '<option value="">All Types</option>';
|
|
|
|
|
|
uniqueTypes.forEach(type => {
|
|
|
const option = document.createElement('option');
|
|
|
option.value = type;
|
|
|
option.textContent = type;
|
|
|
typeFilter.appendChild(option);
|
|
|
});
|
|
|
|
|
|
// Restore selected value if it exists
|
|
|
if (columnFilters.type && uniqueTypes.includes(columnFilters.type)) {
|
|
|
typeFilter.value = columnFilters.type;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Select all filtered fields
|
|
|
function selectAllFilteredFields() {
|
|
|
filteredDataFields.forEach(field => {
|
|
|
selectedFields.set(field.id, field.description);
|
|
|
const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
|
|
|
if (row) {
|
|
|
const checkbox = row.querySelector('.field-checkbox');
|
|
|
checkbox.checked = true;
|
|
|
row.classList.add('selected');
|
|
|
}
|
|
|
});
|
|
|
|
|
|
updateSelectedFieldsDisplay();
|
|
|
updateDataFieldsStats();
|
|
|
updateSelectAllCheckbox();
|
|
|
}
|
|
|
|
|
|
// Clear all selected fields
|
|
|
function clearAllSelectedFields() {
|
|
|
selectedFields.clear();
|
|
|
|
|
|
// Update all checkboxes
|
|
|
document.querySelectorAll('.field-checkbox').forEach(checkbox => {
|
|
|
checkbox.checked = false;
|
|
|
checkbox.closest('tr').classList.remove('selected');
|
|
|
});
|
|
|
|
|
|
updateSelectedFieldsDisplay();
|
|
|
updateDataFieldsStats();
|
|
|
updateSelectAllCheckbox();
|
|
|
}
|
|
|
|
|
|
// Clear only filtered fields
|
|
|
function clearAllFilteredFields() {
|
|
|
filteredDataFields.forEach(field => {
|
|
|
selectedFields.delete(field.id);
|
|
|
const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
|
|
|
if (row) {
|
|
|
const checkbox = row.querySelector('.field-checkbox');
|
|
|
checkbox.checked = false;
|
|
|
row.classList.remove('selected');
|
|
|
}
|
|
|
});
|
|
|
|
|
|
updateSelectedFieldsDisplay();
|
|
|
updateDataFieldsStats();
|
|
|
updateSelectAllCheckbox();
|
|
|
}
|
|
|
|
|
|
// Update the select all checkbox state
|
|
|
function updateSelectAllCheckbox() {
|
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
|
if (!selectAllCheckbox) return;
|
|
|
|
|
|
const allFilteredSelected = filteredDataFields.length > 0 &&
|
|
|
filteredDataFields.every(field => selectedFields.has(field.id));
|
|
|
|
|
|
selectAllCheckbox.checked = allFilteredSelected;
|
|
|
selectAllCheckbox.indeterminate = !allFilteredSelected &&
|
|
|
filteredDataFields.some(field => selectedFields.has(field.id));
|
|
|
}
|
|
|
|
|
|
// Display dataset description
|
|
|
function displayDatasetDescription(description) {
|
|
|
console.log('🎨 Displaying dataset description:', description);
|
|
|
|
|
|
// Check if dataset description element already exists
|
|
|
let descriptionElement = document.getElementById('datasetDescription');
|
|
|
|
|
|
if (!descriptionElement) {
|
|
|
console.log('📌 Creating new dataset description element');
|
|
|
|
|
|
// Create the element if it doesn't exist
|
|
|
const tableContainer = document.getElementById('tableContainer');
|
|
|
const dataFieldsControls = tableContainer.querySelector('.data-fields-controls');
|
|
|
|
|
|
// Create a new div for the dataset description
|
|
|
descriptionElement = document.createElement('div');
|
|
|
descriptionElement.id = 'datasetDescription';
|
|
|
descriptionElement.className = 'dataset-description';
|
|
|
descriptionElement.style.cssText = `
|
|
|
padding: 15px;
|
|
|
background: #e8f5e9;
|
|
|
border: 1px solid #4caf50;
|
|
|
border-radius: 4px;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.5;
|
|
|
`;
|
|
|
|
|
|
// Insert it before the controls
|
|
|
tableContainer.insertBefore(descriptionElement, dataFieldsControls);
|
|
|
console.log('✅ Dataset description element created and inserted');
|
|
|
} else {
|
|
|
console.log('📌 Using existing dataset description element');
|
|
|
}
|
|
|
|
|
|
// Update the content
|
|
|
descriptionElement.innerHTML = `
|
|
|
<strong>Dataset Description:</strong><br>
|
|
|
${description}
|
|
|
`;
|
|
|
console.log('✅ Dataset description content updated');
|
|
|
}
|