// Idea House JavaScript - 处理数据字段选择和Coze API处理 // 全局变量 let dataFields = []; let filteredDataFields = []; let selectedFields = new Map(); // 字段ID -> 描述的映射 // 筛选状态变量 let columnFilters = { id: '', description: '', type: '', coverage: { min: null, max: null }, userCount: null, alphaCount: null }; let sortColumn = null; let sortOrder = 'asc'; // DOM加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { // 设置事件监听器 document.getElementById('loadDataFieldsBtn').addEventListener('click', loadDataFields); document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection); document.getElementById('processFieldsBtn').addEventListener('click', processSelectedFields); // 设置API令牌切换按钮 const toggleBtn = document.getElementById('toggleApiTokenBtn'); const tokenInput = document.getElementById('cozeApiTokenInput'); toggleBtn.addEventListener('click', function() { if (tokenInput.type === 'password') { tokenInput.type = 'text'; toggleBtn.textContent = '隐藏'; } else { tokenInput.type = 'password'; toggleBtn.textContent = '显示'; } }); // 加载保存的配置 loadSavedCozeConfig(); // 设置保存配置按钮 document.getElementById('saveCozeConfigBtn').addEventListener('click', saveCozeConfig); // 设置清除配置按钮 document.getElementById('clearCozeConfigBtn').addEventListener('click', clearCozeConfig); // 设置筛选事件监听器 setupFilterEventListeners(); }); // 从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; // 显示加载状态 document.getElementById('dataFieldsStats').textContent = '正在加载数据字段...'; document.getElementById('tableContainer').style.display = 'none'; try { // 从localStorage或cookie获取会话ID const sessionId = localStorage.getItem('brain_session_id'); if (!sessionId) { alert('请先从主页面连接到BRAIN'); return; } // 调用代理端点 const params = new URLSearchParams({ region: region, delay: delay, universe: universe, dataset_id: datasetId }); // 记录API调用 console.log('🚀 正在调用API获取数据字段和数据集描述'); console.log('📋 参数:', { region: region, delay: delay, universe: universe, dataset_id: datasetId }); // 并行获取数据字段和数据集描述 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('📥 收到响应:'); console.log(' 数据字段状态:', dataFieldsResponse.status); console.log(' 数据集描述状态:', descriptionResponse.status); if (!dataFieldsResponse.ok) { const errorData = await dataFieldsResponse.json(); throw new Error(errorData.error || '获取数据字段失败'); } dataFields = await dataFieldsResponse.json(); // 获取数据集描述(如果可用) let datasetDescription = ''; if (descriptionResponse.ok) { console.log('✅ 数据集描述响应正常,正在解析JSON...'); const descriptionData = await descriptionResponse.json(); console.log('📄 描述数据:', descriptionData); if (descriptionData.success) { datasetDescription = descriptionData.description; // 将其存储在全局中供后续使用 window.currentDatasetDescription = datasetDescription; console.log('✅ 数据集描述已存储:', datasetDescription); } else { console.log('⚠️ 描述响应success=false'); } } else { console.log('❌ 数据集描述响应不正常:', descriptionResponse.status); try { const errorData = await descriptionResponse.json(); console.log('❌ 错误详情:', errorData); } catch (e) { console.log('❌ 无法解析错误响应'); } } // 更新统计信息 document.getElementById('dataFieldsStats').textContent = `已加载 ${dataFields.length} 个数据字段`; // 使用数据集描述填充表格 populateDataFieldsTable(datasetDescription); // 显示表格 document.getElementById('tableContainer').style.display = 'block'; } catch (error) { console.error('加载数据字段失败:', error); document.getElementById('dataFieldsStats').textContent = `错误: ${error.message}`; alert(`加载数据字段失败: ${error.message}`); } } // 使用筛选和排序填充数据字段表格 function populateDataFieldsTable(datasetDescription) { console.log('📊 populateDataFieldsTable 被调用,描述:', 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; // 显示数据集描述(如果可用) if (datasetDescription) { console.log('🎯 显示传入的数据集描述'); displayDatasetDescription(datasetDescription); } else if (window.currentDatasetDescription) { console.log('🎯 显示已存储的数据集描述'); displayDatasetDescription(window.currentDatasetDescription); } else { console.log('⚠️ 无数据集描述可用'); } // 应用筛选器 filteredDataFields = dataFields.filter(field => { // 列特定筛选器 // ID筛选器 if (columnFilters.id && !field.id.toLowerCase().includes(columnFilters.id.toLowerCase())) { return false; } // 描述筛选器 if (columnFilters.description && !field.description.toLowerCase().includes(columnFilters.description.toLowerCase())) { return false; } // 类型筛选器 if (columnFilters.type && field.type !== columnFilters.type) { return false; } // 覆盖率范围筛选器 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; } // 用户数量筛选器 if (columnFilters.userCount !== null && field.userCount < columnFilters.userCount) { return false; } // Alpha数量筛选器 if (columnFilters.alphaCount !== null && field.alphaCount < columnFilters.alphaCount) { return false; } // 高覆盖率筛选器 if (highCoverageFilter && field.coverage < 0.9) { return false; } // 流行度筛选器 if (popularFilter && field.userCount < 1000) { return false; } // 矩阵类型筛选器 if (matrixOnlyFilter && field.type !== 'MATRIX') { return false; } return true; }); // 对筛选后的数据字段排序 if (sortColumn) { filteredDataFields.sort((a, b) => { let aVal = a[sortColumn]; let bVal = b[sortColumn]; // 处理数值 if (sortColumn === 'coverage' || sortColumn === 'userCount' || sortColumn === 'alphaCount') { aVal = Number(aVal); bVal = Number(bVal); } else { // 字符串比较 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; }); } // 清空表格 tableBody.innerHTML = ''; if (filteredDataFields.length === 0) { tableBody.innerHTML = '未找到符合筛选条件的数据字段'; updateDataFieldsStats(); return; } // 创建表格行 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'} `; // 为行添加点击处理程序 row.addEventListener('click', function(e) { if (e.target.type !== 'checkbox') { const checkbox = row.querySelector('.field-checkbox'); checkbox.checked = !checkbox.checked; handleFieldSelection(checkbox); } }); // 为复选框添加变更处理程序 const checkbox = row.querySelector('.field-checkbox'); checkbox.addEventListener('change', function() { handleFieldSelection(this); }); tableBody.appendChild(row); }); // 更新统计信息并填充类型筛选器 updateDataFieldsStats(); populateTypeFilter(); updateSelectAllCheckbox(); } // 处理字段选择 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(); } // 更新选中字段的显示 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); }); } // 清除所有选择 function clearSelection() { selectedFields.clear(); // 取消选中所有复选框并移除选中类 document.querySelectorAll('.field-checkbox').forEach(checkbox => { checkbox.checked = false; checkbox.closest('tr').classList.remove('selected'); }); updateSelectedFieldsDisplay(); updateDataFieldsStats(); updateSelectAllCheckbox(); } // 使用Coze API处理选中的字段 async function processSelectedFields() { if (selectedFields.size === 0) { alert('请至少选择一个字段'); return; } // 显示加载覆盖层,包含Coze API特定消息 const loadingOverlay = document.getElementById('loadingOverlay'); const loadingContent = loadingOverlay.querySelector('.loading-content'); loadingContent.innerHTML = `

🚀 正在向Coze API发送请求...

正在通过Coze工作流处理 ${selectedFields.size} 个选中的字段

📡 正在连接到Coze API...
⚙️ 正在运行工作流分析...
📊 正在生成洞察...

`; loadingOverlay.style.display = 'flex'; try { // 准备所需格式的数据 {"id":"description"} const selectedFieldsObject = {}; selectedFields.forEach((description, fieldId) => { selectedFieldsObject[fieldId] = description; }); // 获取Coze API配置 const cozeApiToken = document.getElementById('cozeApiTokenInput').value; const workflowId = document.getElementById('workflowIdInput').value; // 验证输入 if (!cozeApiToken) { alert('请输入Coze API令牌'); document.getElementById('loadingOverlay').style.display = 'none'; return; } if (!workflowId) { alert('请输入工作流ID'); document.getElementById('loadingOverlay').style.display = 'none'; return; } // 更新加载消息以显示API调用正在进行 loadingContent.innerHTML = `

📡 Coze API请求进行中...

工作流ID: ${workflowId}

选中的字段: ${Object.keys(selectedFieldsObject).join(', ')}

✅ API凭据已验证
🔄 正在向Coze服务器发送请求...
⏳ 请等待响应...

`; console.log('🚀 开始Coze API请求...'); console.log('📋 选中的字段:', selectedFieldsObject); console.log('🔑 使用以以下结尾的API令牌:', cozeApiToken.slice(-10)); console.log('⚙️ 工作流ID:', workflowId); // 调用处理端点 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('📡 收到服务器响应'); const result = await response.json(); if (!response.ok) { console.error('❌ Coze API请求失败:', result.error); throw new Error(result.error || '处理字段失败'); } console.log('✅ Coze API请求成功!'); console.log('📊 结果:', result); // 更新加载消息以显示成功 loadingContent.innerHTML = `

✅ 收到Coze API响应!

已成功通过工作流处理

📥 收到来自Coze的响应
🎉 正在格式化结果...

`; // 短暂延迟以显示成功消息 await new Promise(resolve => setTimeout(resolve, 1000)); // 显示结果 displayResults(result); } catch (error) { console.error('💥 通过Coze API处理字段失败:', error); alert(`通过Coze API处理字段失败: ${error.message}`); } finally { // 隐藏加载覆盖层 document.getElementById('loadingOverlay').style.display = 'none'; // 为下次使用重置加载内容 loadingContent.innerHTML = `

处理中...

请等待我们分析您选中的字段...

`; } } // 以markdown格式显示结果 function displayResults(result) { const resultsSection = document.getElementById('resultsSection'); const resultsContent = document.getElementById('resultsContent'); // 显示结果部分 resultsSection.style.display = 'block'; // 将结果格式化为markdown - 简化版本 let markdown = '# 分析结果\n\n'; // 添加选中字段部分 markdown += '## 选中的字段\n\n'; Object.entries(result.selected_fields).forEach(([fieldId, description]) => { markdown += `- **${fieldId}**: ${description}\n`; }); markdown += '\n'; // 添加输出部分 - 仅显示实际分析输出 markdown += '## 分析输出\n\n'; if (result.output) { // 如果输出已经是格式化文本,直接使用 if (typeof result.output === 'string') { markdown += result.output; } else { // 如果是对象,尝试美观地显示 markdown += '```json\n'; markdown += JSON.stringify(result.output, null, 2); markdown += '\n```\n'; } } else { markdown += '_无输出数据可用_'; } // 将markdown渲染为HTML resultsContent.innerHTML = renderMarkdown(markdown); // 滚动到结果部分 resultsSection.scrollIntoView({ behavior: 'smooth' }); } // 辅助函数格式化markdown(可选增强功能) function renderMarkdown(markdown) { // 这是一个改进的markdown渲染器,能更好地处理列表 let html = markdown; // 首先,转义HTML以防止XSS html = html.replace(/&/g, '&') .replace(//g, '>'); // 代码块(必须在行内代码之前) html = html.replace(/```([\s\S]*?)```/g, function(match, code) { return '
' + code.trim() + '
'; }); // 标题 html = html.replace(/^#### (.*$)/gim, '

$1

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

$1

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

$1

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

$1

'); // 粗体(必须在斜体之前) html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // 斜体 html = html.replace(/\*([^*\n]+)\*/g, '$1'); // 行内代码 html = html.replace(/`([^`]+)`/g, '$1'); // 更仔细地处理列表 // 为了更好的列表处理,分割成行 const lines = html.split('\n'); let inList = false; let processedLines = []; for (let i = 0; i < lines.length; i++) { let line = lines[i]; // 检查行是否是列表项 if (line.match(/^[\*\-\+] /)) { // 用适当的HTML替换列表标记 line = line.replace(/^[\*\-\+] (.*)$/, '
  • $1
  • '); // 如果不在列表中,开始一个列表 if (!inList) { processedLines.push(''); processedLines.push('
      '); inList = true; } processedLines.push(line); } else { // 不是列表项 if (inList) { // 关闭列表 if (i > 0 && lines[i-1].match(/^\d+\. /)) { processedLines.push('
    '); } else { processedLines.push(''); } inList = false; } processedLines.push(line); } } // 关闭任何剩余的列表 if (inList) { if (lines[lines.length - 1].match(/^\d+\. /)) { processedLines.push(''); } else { processedLines.push(''); } } html = processedLines.join('\n'); // 换行 - 将双换行符转换为段落 html = html.replace(/\n\n/g, '

    '); html = '

    ' + html + '

    '; // 清理空段落 html = html.replace(/

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

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

    (