// 特征工程 JavaScript // 将API密钥存储在会话存储中 let apiKey = sessionStorage.getItem('deepseekApiKey'); let currentStep = parseInt(sessionStorage.getItem('featureEngCurrentStep')) || 1; let pipelineSteps = JSON.parse(sessionStorage.getItem('featureEngPipelineSteps')) || []; let currentOptions = JSON.parse(sessionStorage.getItem('featureEngCurrentOptions')) || []; let currentDataState = sessionStorage.getItem('featureEngCurrentDataState') || '原始数据'; let conversationHistory = JSON.parse(sessionStorage.getItem('featureEngConversationHistory')) || []; let customSystemPrompt = sessionStorage.getItem('customSystemPrompt') || null; // DOM元素 const apiKeyInput = document.getElementById('apiKey'); const saveApiKeyBtn = document.getElementById('saveApiKey'); const loadQuestionTemplateBtn = document.getElementById('loadQuestionTemplate'); const editSystemPromptBtn = document.getElementById('editSystemPrompt'); const questionTemplateInput = document.getElementById('questionTemplate'); const startPipelineBtn = document.getElementById('startPipeline'); const systemPromptModal = document.getElementById('systemPromptModal'); const systemPromptTextarea = document.getElementById('systemPromptTextarea'); const loadDefaultPromptBtn = document.getElementById('loadDefaultPrompt'); const initialSetupSection = document.getElementById('initialSetup'); const optionsSection = document.getElementById('optionsSection'); const optionsContainer = document.getElementById('optionsContainer'); const clearOptionsBtn = document.getElementById('clearOptions'); const exportPipelineBtn = document.getElementById('exportPipeline'); const pipelineStatus = document.getElementById('pipelineStatus'); const pipelineStepsDiv = document.getElementById('pipelineSteps'); const modalOverlay = document.getElementById('modalOverlay'); const categoryPopup = document.getElementById('categoryPopup'); const categoryPopupTitle = document.getElementById('categoryPopupTitle'); const categoryPopupDescription = document.getElementById('categoryPopupDescription'); const categoryPopupOperators = document.getElementById('categoryPopupOperators'); const categoryPopupOperatorsTitle = document.getElementById('categoryPopupOperatorsTitle'); // 如果存在API密钥则初始化 if (apiKey) { apiKeyInput.value = apiKey; } // 页面加载时加载现有对话状态 window.addEventListener('DOMContentLoaded', () => { console.log('正在加载对话状态...'); console.log('对话历史:', conversationHistory); console.log('当前步骤:', currentStep); console.log('流水线步骤:', pipelineSteps); console.log('当前选项:', currentOptions); // 如果有对话历史,显示当前选项 if (conversationHistory.length > 0 && currentOptions.length > 0) { console.log('正在恢复对话状态...'); initialSetupSection.style.display = 'none'; optionsSection.style.display = 'block'; displayOptions(); updatePipelineStatus(); } else { // 确保从干净状态开始 console.log('从干净状态开始...'); initialSetupSection.style.display = 'block'; optionsSection.style.display = 'none'; } }); // 点击遮罩层时关闭模态框 modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { modalOverlay.classList.remove('active'); // 查找正在编辑的卡片并取消编辑 const editingCard = document.querySelector('.option-card.editing'); if (editingCard) { const index = parseInt(editingCard.dataset.optionIndex); cancelEdit(index); } } }); // 保存API密钥并测试连接 saveApiKeyBtn.addEventListener('click', async () => { const newApiKey = apiKeyInput.value.trim(); if (!newApiKey) { showNotification('请输入有效的API密钥', 'error'); return; } try { showLoading('正在测试API连接...'); const response = await fetch('/feature-engineering/api/test-deepseek', { method: 'POST', headers: { 'X-API-Key': newApiKey, 'Content-Type': 'application/json' } }); const data = await response.json(); if (response.ok && data.success) { sessionStorage.setItem('deepseekApiKey', newApiKey); apiKey = newApiKey; showNotification('API连接成功', 'success'); } else { showNotification(`API错误: ${data.error || '未知错误'}`, 'error'); console.error('API错误详情:', data); } } catch (error) { showNotification('测试API连接时出错: ' + error.message, 'error'); console.error('API测试错误:', error); } finally { hideLoading(); } }); // 加载问题模板 loadQuestionTemplateBtn.addEventListener('click', () => { const template = `当前步骤: 0 当前数据字段: modify_your_input 当前数据字段描述: input_datafield_description 初始EDA观察: input_datafield_eda_observation 先前使用的步骤和类别: 无 当前数据状态: 这是第一步原始数据`; questionTemplateInput.value = template; showNotification('问题模板已加载', 'success'); }); // 编辑系统提示 editSystemPromptBtn.addEventListener('click', () => { // 加载当前系统提示或默认提示 if (customSystemPrompt) { systemPromptTextarea.value = customSystemPrompt; } else { loadDefaultSystemPrompt(); } systemPromptModal.style.display = 'block'; }); // 加载默认系统提示 loadDefaultPromptBtn.addEventListener('click', loadDefaultSystemPrompt); // 点击外部时隐藏类别弹出窗口 document.addEventListener('click', (e) => { if (!categoryPopup.contains(e.target) && !e.target.classList.contains('clickable-category')) { hideCategoryPopup(); } }); async function loadDefaultSystemPrompt() { try { showLoading('正在加载默认系统提示...'); const response = await fetch('/feature-engineering/api/get-default-system-prompt', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (response.ok && data.success) { systemPromptTextarea.value = data.default_system_prompt; showNotification('默认系统提示已从后端加载', 'success'); } else { showNotification(`加载默认提示时出错: ${data.error || '未知错误'}`, 'error'); console.error('加载默认提示时出错:', data); } } catch (error) { showNotification('加载默认系统提示时出错: ' + error.message, 'error'); console.error('加载默认提示时出错:', error); } finally { hideLoading(); } } // 关闭系统提示模态框 function closeSystemPromptModal() { systemPromptModal.style.display = 'none'; } // 保存系统提示 function saveSystemPrompt() { const prompt = systemPromptTextarea.value.trim(); if (!prompt) { showNotification('系统提示不能为空', 'error'); return; } customSystemPrompt = prompt; sessionStorage.setItem('customSystemPrompt', prompt); systemPromptModal.style.display = 'none'; showNotification('系统提示保存成功', 'success'); } // 启动特征工程流水线 startPipelineBtn.addEventListener('click', async () => { if (!apiKey) { showNotification('请先配置您的Deepseek API密钥', 'error'); return; } const questionTemplate = questionTemplateInput.value.trim(); if (!questionTemplate) { showNotification('请加载或输入问题模板', 'error'); return; } try { showLoading('正在获取AI推荐...'); console.log('=== 启动新流水线 ==='); console.log('开始前的当前对话历史:', conversationHistory); console.log('对话历史长度:', conversationHistory.length); const response = await fetch('/feature-engineering/api/continue-conversation', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ conversation_history: [], user_message: questionTemplate, custom_system_prompt: customSystemPrompt }) }); const data = await response.json(); console.log('=== 初始提示 ==='); console.log('用户消息:', questionTemplate); console.log('=== AI响应 ==='); console.log('AI响应:', data.response); console.log('=================='); if (response.ok && data.success) { // 清除对话历史并为新流水线重置状态 conversationHistory = []; currentStep = 1; pipelineSteps = []; currentDataState = '原始数据'; // 添加到对话历史 conversationHistory.push({ role: 'user', content: questionTemplate }); conversationHistory.push({ role: 'assistant', content: data.response }); console.log('初始后的对话历史:', conversationHistory); console.log('对话历史长度:', conversationHistory.length); // 解析AI响应以提取选项 parseAIResponse(data.response); // 保存对话状态 saveConversationState(); // 显示选项部分并隐藏初始设置 initialSetupSection.style.display = 'none'; optionsSection.style.display = 'block'; updatePipelineStatus(); showNotification('AI推荐加载成功', 'success'); } else { showNotification(`错误: ${data.error || '未知错误'}`, 'error'); console.error('API错误详情:', data); } } catch (error) { showNotification('获取推荐时出错: ' + error.message, 'error'); console.error('流水线启动错误:', error); } finally { hideLoading(); } }); // 解析AI响应以提取选项 function parseAIResponse(response) { console.log('=== 解析AI响应 ==='); console.log('原始响应:', response); currentOptions = []; // 动态内容清理 - 移除各种摘要部分 let cleanResponse = response; const summaryPatterns = [ /### \*\*最佳选择\?\*\*[\s\S]*$/i, /### \*\*推荐下一步:\*\*[\s\S]*$/i, /最推荐的选择:[\s\S]*$/i, /理由:[\s\S]*$/i, /这保持了[\s\S]*$/i, /您想继续吗[\s\S]*$/i, /\*要创建的特征示例:\*[\s\S]*$/i ]; summaryPatterns.forEach(pattern => { cleanResponse = cleanResponse.replace(pattern, ''); }); console.log('清理后的响应:', cleanResponse); // 动态提取顶级上下文 let globalContext = null; const contextPatterns = [ /\*\*上下文:\*\*\s*([\s\S]*?)(?=###|####|\*\*选项|\*\*选择|选项\s+\d+|$)/i, /上下文:\s*([\s\S]*?)(?=###|####|\*\*选项|\*\*选择|选项\s+\d+|$)/i ]; for (const pattern of contextPatterns) { const match = cleanResponse.match(pattern); if (match) { globalContext = match[1].trim(); console.log('找到全局上下文:', globalContext); break; } } // 动态选项模式匹配 const optionPatterns = [ /(?:####\s*)?(?:\*\*)?选项\s+(\d+)\s+用于\s+步骤\s+(\d+):?\*?\*?\s*([\s\S]*?)(?=(?:####\s*)?(?:\*\*)?选项\s+\d+\s+用于\s+步骤\s+\d+:|最推荐|理由:|这保持了|$)/gi, /(?:####\s*)?(?:\*\*)?option\s+(\d+)\s+for\s+Step\s+(\d+):\s*([\s\S]*?)(?=(?:####\s*)?(?:\*\*)?option\s+\d+\s+for\s+Step\s+\d+:|最推荐|理由:|这保持了|$)/gi ]; let optionsFound = false; for (const optionPattern of optionPatterns) { let match; const tempOptions = []; while ((match = optionPattern.exec(cleanResponse)) !== null) { const optionNumber = match[1]; const stepNumber = match[2]; const content = match[3].trim(); console.log(`找到步骤 ${stepNumber} 的选项 ${optionNumber}:`, content); const parsedOption = parseOptionContent(content, globalContext, parseInt(optionNumber), parseInt(stepNumber)); if (parsedOption) { tempOptions.push(parsedOption); } } if (tempOptions.length > 0) { currentOptions = tempOptions; optionsFound = true; break; } // 为下一个模式重置regex lastIndex optionPattern.lastIndex = 0; } if (!optionsFound) { console.log('标准模式未找到选项,尝试备用解析...'); // 备用方案:尝试查找任何编号选项 const fallbackPattern = /(\d+)[.)]\s*([\s\S]*?)(?=\d+[.)]|$)/g; let match; while ((match = fallbackPattern.exec(cleanResponse)) !== null) { const optionNumber = match[1]; const content = match[2].trim(); console.log(`备用方案找到选项 ${optionNumber}:`, content); const parsedOption = parseOptionContent(content, globalContext, parseInt(optionNumber), currentStep); if (parsedOption) { currentOptions.push(parsedOption); } } } // 确保所有选项具有相同的上下文(如果需要从第一个复制) if (currentOptions.length > 0 && currentOptions[0].context) { const sharedContext = currentOptions[0].context; currentOptions.forEach(option => { if (!option.context || option.context.includes('同上')) { option.context = sharedContext; } }); } console.log('解析的选项总数:', currentOptions.length); console.log('当前选项:', currentOptions); console.log('========================'); displayOptions(); // 保存当前选项状态 saveConversationState(); } // 辅助函数解析单个选项内容 function parseOptionContent(content, globalContext, optionNumber, stepNumber) { console.log('=== 解析选项内容 ==='); console.log('原始内容:', content); // 更精确的模式用于确切格式 const contextPatterns = [ /上下文:\s*([\s\S]*?)(?=\s+选择下一步:)/i, /\*\*上下文:\*\*\s*([\s\S]*?)(?=\s+\*\*选择下一步:\*\*)/i, /上下文:\s*([\s\S]*?)(?=\s+\*\*选择下一步:\*\*)/i ]; // 多个模式用于下一步提取 const nextStepPatterns = [ /选择下一步:\s*([^\n\r]+?)(?=\s+理由:)/i, /\*\*选择下一步:\*\*\s*([^\n\r]+?)(?=\s+\*\*理由:\*\*)/i, /选择下一步:\s*([^\n\r]+?)(?=\s+\*\*理由:\*\*)/i ]; // 多个模式用于理由提取 const reasonPatterns = [ /理由:\s*([\s\S]*?)(?=最推荐|理由:|这保持了|$)/i, /\*\*理由:\*\*\s*([\s\S]*?)(?=最推荐|理由:|这保持了|$)/i ]; let contextMatch = null; let nextStepMatch = null; let reasonMatch = null; // 尝试上下文模式 for (const pattern of contextPatterns) { contextMatch = content.match(pattern); if (contextMatch) { console.log('上下文模式匹配:', pattern); console.log('上下文匹配:', contextMatch[1].trim()); break; } } // 尝试下一步模式 for (const pattern of nextStepPatterns) { nextStepMatch = content.match(pattern); if (nextStepMatch) { console.log('下一步模式匹配:', pattern); console.log('下一步匹配:', nextStepMatch[1].trim()); break; } } // 尝试理由模式 for (const pattern of reasonPatterns) { reasonMatch = content.match(pattern); if (reasonMatch) { console.log('理由模式匹配:', pattern); console.log('理由匹配:', reasonMatch[1].trim()); break; } } console.log('解析结果:', { contextMatch: contextMatch ? contextMatch[1].trim() : null, nextStepMatch: nextStepMatch ? nextStepMatch[1].trim() : null, reasonMatch: reasonMatch ? reasonMatch[1].trim() : null, globalContext: globalContext ? '可用' : '不可用' }); // 确定要使用的上下文 - 优先使用单个选项上下文而非全局上下文 let context = null; if (contextMatch) { context = contextMatch[1].trim().replace(/同上/gi, '').trim(); console.log('使用单个选项上下文:', context); } else if (globalContext) { context = globalContext; console.log('使用全局上下文:', context); } if ((context || contextMatch) && nextStepMatch && reasonMatch) { const result = { optionNumber: optionNumber, stepNumber: stepNumber, context: context, nextStep: nextStepMatch[1].trim().replace(/\*\*/g, ''), reason: reasonMatch[1].trim(), originalContent: content }; result.reason = "我在这一步使用了xxxxxxx操作符" + ",目的是\n" + result.reason; console.log('成功解析选项:', result); console.log('最终存储的上下文:', result.context); console.log('==============================='); return result; } else { console.log('解析选项内容失败:', { hasContext: !!(context || contextMatch), hasNextStep: !!nextStepMatch, hasReason: !!reasonMatch }); console.log('==============================='); return null; } } // 将选项显示为卡片 function displayOptions() { optionsContainer.innerHTML = ''; currentOptions.forEach((option, index) => { const card = createOptionCard(option, index); optionsContainer.appendChild(card); }); } // 创建选项卡片 function createOptionCard(option, index) { console.log('=== 创建选项卡片 ==='); console.log('选项索引:', index); console.log('显示的选项上下文:', option.context); console.log('选项下一步:', option.nextStep); console.log('选项理由:', option.reason); console.log('============================'); const card = document.createElement('div'); card.className = 'option-card'; card.dataset.optionIndex = index; card.innerHTML = `
选项 ${option.optionNumber}
${option.nextStep}
`; // 创建卡片后自动调整文本区域大小 setTimeout(() => { const textareas = card.querySelectorAll('.auto-resize-textarea'); textareas.forEach(autoResizeTextarea); }, 0); return card; } // 自动调整文本区域大小函数 function autoResizeTextarea(textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.max(textarea.scrollHeight, 60) + 'px'; } // 选择并编辑选项 function selectAndEdit(index) { const card = document.querySelector(`[data-option-index="${index}"]`); const fields = card.querySelectorAll('.option-field'); // 移除只读类并使字段可编辑 fields.forEach(field => { field.classList.remove('readonly'); const input = field.querySelector('input, textarea'); const readonlyDisplay = field.querySelector('.readonly-display'); if (input) { input.removeAttribute('readonly'); // 对于下一步字段,显示输入框并隐藏只读显示 if (readonlyDisplay) { input.style.display = 'block'; readonlyDisplay.style.display = 'none'; } } // 为文本区域添加自动调整大小功能 if (input && input.tagName === 'TEXTAREA') { input.addEventListener('input', () => autoResizeTextarea(input)); autoResizeTextarea(input); // 初始调整大小 } }); // 更新卡片状态并显示模态遮罩层 card.classList.add('editing'); modalOverlay.classList.add('active'); // 更新操作按钮 const actionsDiv = card.querySelector('.option-actions'); actionsDiv.innerHTML = ` `; } // 保存选项 function saveOption(index) { const card = document.querySelector(`[data-option-index="${index}"]`); const contextTextarea = card.querySelector('.option-field:nth-child(1) textarea'); const nextStepInput = card.querySelector('.option-field:nth-child(2) input'); const reasonTextarea = card.querySelector('.option-field:nth-child(3) textarea'); // 更新选项数据 currentOptions[index].context = contextTextarea.value; currentOptions[index].nextStep = nextStepInput.value; currentOptions[index].reason = reasonTextarea.value; // 保存更新的选项状态 saveConversationState(); // 再次使字段变为只读 const fields = card.querySelectorAll('.option-field'); fields.forEach(field => { field.classList.add('readonly'); const input = field.querySelector('input, textarea'); const readonlyDisplay = field.querySelector('.readonly-display'); if (input) { input.setAttribute('readonly', 'readonly'); // 对于下一步字段,隐藏输入框并显示只读显示 if (readonlyDisplay) { input.style.display = 'none'; readonlyDisplay.style.display = 'block'; // 更新可点击类别文本 const categorySpan = readonlyDisplay.querySelector('.clickable-category'); if (categorySpan) { categorySpan.textContent = input.value; categorySpan.setAttribute('onclick', `showCategoryPopup('${input.value.replace(/'/g, "\\'")}', event)`); } } } }); // 更新卡片状态并隐藏模态遮罩层 card.classList.remove('editing'); modalOverlay.classList.remove('active'); // 更新操作按钮 const actionsDiv = card.querySelector('.option-actions'); actionsDiv.innerHTML = ` `; showNotification('选项保存成功', 'success'); } // 取消编辑 function cancelEdit(index) { // 隐藏模态遮罩层 modalOverlay.classList.remove('active'); // 使用原始数据刷新卡片 const card = createOptionCard(currentOptions[index], index); const oldCard = document.querySelector(`[data-option-index="${index}"]`); oldCard.parentNode.replaceChild(card, oldCard); } // 从类别获取数据状态 function getDataStateFromCategory(category) { const stateMap = { '基础算术和数学运算': '数学变换', '逻辑和条件运算': '条件过滤', '时间序列:变化检测和值比较': '变化分析', '时间序列:统计特征工程': '统计工程', '时间序列:排名、缩放和归一化': '排名和归一化', '时间序列:衰减、平滑和周转控制': '平滑和控制', '时间序列:极值和位置识别': '极值识别', '横截面:排名、缩放和归一化': '横截面归一化', '横截面:回归和中性化': '中性化', '横截面:分布变换和截断': '分布变换', '变换和过滤操作': '变换和过滤', '分组聚合和统计摘要': '聚合', '分组排名、缩放和归一化': '分组归一化', '分组回归和中性化': '分组中性化', '分组插补和回填': '插补和回填' }; return stateMap[category] || '已处理'; } // 清除选项并重新开始 clearOptionsBtn.addEventListener('click', () => { if (confirm('确定要清除所有进度并重新开始吗?')) { // 清除所有状态 conversationHistory = []; currentStep = 1; pipelineSteps = []; currentOptions = []; currentDataState = '原始数据'; // 清除会话存储 sessionStorage.removeItem('featureEngConversationHistory'); sessionStorage.removeItem('featureEngCurrentStep'); sessionStorage.removeItem('featureEngPipelineSteps'); sessionStorage.removeItem('featureEngCurrentOptions'); sessionStorage.removeItem('featureEngCurrentDataState'); // 重置UI optionsSection.style.display = 'none'; initialSetupSection.style.display = 'block'; questionTemplateInput.value = ''; // 更新流水线状态以反映清除状态 updatePipelineStatus(); showNotification('流水线已清除。您可以开始新的对话。', 'success'); } }); // 导出流水线 exportPipelineBtn.addEventListener('click', () => { const exportData = { timestamp: new Date().toISOString(), currentStep: currentStep, pipelineSteps: pipelineSteps, currentOptions: currentOptions, conversationHistory: conversationHistory }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `feature_engineering_pipeline_${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification('流水线导出成功', 'success'); }); // 发送编辑后的选项并继续 function sendAndContinue(index) { const card = document.querySelector(`[data-option-index="${index}"]`); const contextTextarea = card.querySelector('.option-field:nth-child(1) textarea'); const nextStepInput = card.querySelector('.option-field:nth-child(2) input'); const reasonTextarea = card.querySelector('.option-field:nth-child(3) textarea'); // 获取编辑后的值 const context = contextTextarea.value; const chosenStep = nextStepInput.value; const reason = reasonTextarea.value; console.log('=== 发送并继续调试 ==='); console.log('选中的选项索引:', index); console.log('当前选项:', currentOptions[index]); console.log('上下文:', context); console.log('选择的步骤:', chosenStep); console.log('理由:', reason); console.log('更新前的当前步骤:', currentStep); console.log('更新前的流水线步骤:', pipelineSteps); // 隐藏模态遮罩层 modalOverlay.classList.remove('active'); // 添加到流水线步骤 - 修复:使用currentStep而不是选项中的stepNumber pipelineSteps.push(`步骤 ${currentStep}: ${chosenStep}`); currentStep = currentStep + 1; // 从当前步骤递增 currentDataState = getDataStateFromCategory(chosenStep); console.log('更新后的当前步骤:', currentStep); console.log('更新后的流水线步骤:', pipelineSteps); console.log('当前数据状态:', currentDataState); // 更新流水线状态 updatePipelineStatus(); // 保存流水线状态 saveConversationState(); // 为AI系统提示准备适当格式的消息 // 构建先前步骤列表 const previousStepsText = pipelineSteps.length > 0 ? pipelineSteps.join(', ') : '无'; // 获取所选步骤的类别描述 const categoryData = operatorsData.find(cat => cat.name === chosenStep); const stepDescription = categoryData ? categoryData.description : '无描述可用'; const userMessage = ` 我选择的下一步: ${chosenStep} 步骤描述: ${stepDescription} 选择理由: ${reason} 基于我的选择和信息,请推荐一些进一步的选项`; console.log('=== 为AI构建的消息 ==='); console.log('发送的用户消息:', userMessage); console.log('当前步骤:', currentStep); console.log('先前步骤:', previousStepsText); console.log('当前数据状态:', currentDataState); console.log('步骤描述:', stepDescription); console.log('选择的下一步:', chosenStep); console.log('选择理由:', reason); console.log('================================='); // 获取下一个推荐 showLoading('正在获取下一个推荐...'); fetch('/feature-engineering/api/continue-conversation', { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ conversation_history: conversationHistory, user_message: userMessage, custom_system_prompt: customSystemPrompt }) }) .then(response => response.json()) .then(data => { console.log('=== 发送并继续提示 ==='); console.log('用户消息:', userMessage); console.log('之前的当前步骤:', currentStep); console.log('之前的流水线步骤:', pipelineSteps); console.log('发送的对话历史:', conversationHistory); console.log('=== AI响应 ==='); console.log('AI响应:', data.response); console.log('=================='); if (data.success) { // 添加到对话历史 conversationHistory.push({ role: 'user', content: userMessage }); conversationHistory.push({ role: 'assistant', content: data.response }); console.log('更新后的对话历史:', conversationHistory); // 解析新的AI响应 parseAIResponse(data.response); // 保存对话状态 saveConversationState(); showNotification(`编辑的选项发送成功。下一步推荐已加载。`, 'success'); } else { showNotification(`错误: ${data.error || '未知错误'}`, 'error'); console.error('API错误详情:', data); } }) .catch(error => { showNotification('获取下一个推荐时出错: ' + error.message, 'error'); console.error('下一步错误:', error); }) .finally(() => { hideLoading(); }); } // 使函数在onclick处理程序中全局可用 window.selectAndEdit = selectAndEdit; window.saveOption = saveOption; window.cancelEdit = cancelEdit; window.sendAndContinue = sendAndContinue; window.closeSystemPromptModal = closeSystemPromptModal; window.saveSystemPrompt = saveSystemPrompt; // 更新流水线状态 function updatePipelineStatus() { console.log('=== 更新流水线状态 ==='); console.log('流水线步骤:', pipelineSteps); console.log('当前步骤:', currentStep); console.log('当前数据状态:', currentDataState); if (pipelineSteps.length === 0) { pipelineStatus.style.display = 'none'; return; } pipelineStatus.style.display = 'block'; pipelineStepsDiv.innerHTML = pipelineSteps.map(step => `
${step}
` ).join(''); // 添加当前状态 const statusDiv = document.createElement('div'); statusDiv.className = 'pipeline-step'; statusDiv.style.backgroundColor = '#e8f5e8'; statusDiv.innerHTML = `当前步骤: ${currentStep} | 数据状态: ${currentDataState}`; pipelineStepsDiv.appendChild(statusDiv); console.log('流水线状态已更新'); console.log('=============================='); } // 工具函数 function showNotification(message, type) { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 8000); } let loadingElement = null; function showLoading(message) { loadingElement = document.createElement('div'); loadingElement.className = 'loading-overlay'; loadingElement.innerHTML = `
${message}
`; document.body.appendChild(loadingElement); } function hideLoading() { if (loadingElement) { loadingElement.remove(); loadingElement = null; } } // 操作符参考数据 const operatorsData = [ { id: 1, name: "基础算术和数学运算", description: "核心数学和逐元素运算(例如,加、减、乘、对数、指数、绝对值、幂等)", operators: ["add", "subtract", "multiply", "divide", "exp", "log", "abs", "power", "sqrt", "round", "round_down", "floor", "ceiling", "inverse", "negate", "signed_power", "sign", "arc_sin", "arc_cos", "arc_tan", "tanh", "sigmoid", "s_log_1p", "fraction", "max", "min", "densify", "pasteurize", "purify", "to_nan", "nan_out", "replace", "reverse"] }, { id: 2, name: "逻辑和条件运算", description: "布尔逻辑、比较和条件分支(例如,与、或、非、如果否则、等于、大于、小于等)", operators: ["and", "or", "not", "if_else", "equal", "not_equal", "less", "less_equal", "greater", "greater_equal", "is_nan", "is_not_nan", "is_finite", "is_not_finite", "nan_mask"] }, { id: 3, name: "时间序列:变化检测和值比较", description: "比较随时间变化的值,计算差异,检测变化,或计算自上次变化以来的天数(例如,ts_delta、ts_returns、days_from_last_change、last_diff_value等)", operators: ["ts_delta", "ts_returns", "days_from_last_change", "last_diff_value", "ts_delta_limit", "ts_backfill"] }, { id: 4, name: "时间序列:统计特征工程", description: "计算随时间滚动的统计属性(例如,ts_mean、ts_std_dev、ts_skewness、ts_kurtosis、ts_entropy、ts_moment、ts_covariance、ts_corr、ts_co_skewness、ts_co_kurtosis等)", operators: ["ts_mean", "ts_std_dev", "ts_skewness", "ts_kurtosis", "ts_entropy", "ts_moment", "ts_covariance", "ts_corr", "ts_partial_corr", "ts_triple_corr", "ts_ir", "ts_sum", "ts_product", "ts_median", "ts_count_nans", "ts_av_diff", "ts_regression", "ts_poly_regression", "ts_vector_neut", "ts_vector_proj", "ts_co_skewness", "ts_co_kurtosis", "ts_theilsen", "ts_zscore", "ts_rank_gmean_amean_diff", "ts_step", "ts_delay", "inst_tvr", "generate_stats"] }, { id: 5, name: "时间序列:排名、缩放和归一化", description: "在滚动窗口内对时间序列数据进行排名、缩放或归一化(例如,ts_rank、ts_scale、ts_percentage、ts_quantile等)", operators: ["ts_rank", "ts_scale", "ts_percentage", "ts_quantile", "ts_rank_gmean_amean_diff", "ts_zscore"] }, { id: 6, name: "时间序列:衰减、平滑和周转控制", description: "在时间序列中应用衰减(线性、指数、加权)、平滑或控制周转(例如,ts_decay_exp_window、ts_decay_linear、ts_weighted_decay、ts_target_tvr_decay、hump、jump_decay等)", operators: ["ts_decay_exp_window", "ts_decay_linear", "ts_weighted_decay", "ts_target_tvr_decay", "hump", "jump_decay", "ts_target_tvr_delta_limit", "ts_target_tvr_hump", "hump_decay"] }, { id: 7, name: "时间序列:极值和位置识别", description: "识别最小/最大值、它们的差异或窗口内极值的位置(索引)(例如,ts_min、ts_max、ts_min_diff、ts_max_diff、ts_arg_min、ts_arg_max、ts_min_max_diff等)", operators: ["ts_min", "ts_max", "ts_min_diff", "ts_max_diff", "ts_arg_min", "ts_arg_max", "ts_min_max_diff", "ts_min_max_cps", "kth_element"] }, { id: 8, name: "横截面:排名、缩放和归一化", description: "在单个时间点跨工具对数据进行排名、缩放、归一化或标准化(例如,rank、zscore、scale_down、normalize、rank_by_side等)", operators: ["rank", "zscore", "scale_down", "scale", "normalize", "rank_by_side", "generalized_rank", "one_side", "rank_gmean_amean_diff"] }, { id: 9, name: "横截面:回归和中性化", description: "移除其他变量的影响,执行横截面回归,或将一个向量相对于另一个向量正交化(例如,regression_neut、vector_neut、regression_proj、vector_proj、multi_regression等)", operators: ["regression_neut", "vector_neut", "regression_proj", "vector_proj", "multi_regression"] }, { id: 10, name: "横截面:分布变换和截断", description: "跨工具变换分布或截断异常值(例如,quantile、winsorize、truncate、bucket、generalized_rank等)", operators: ["quantile", "winsorize", "truncate", "bucket", "right_tail", "left_tail", "tail"] }, { id: 11, name: "变换和过滤操作", description: "通用数据变换、过滤、钳制、掩码或条件值分配(例如,filter、clamp、keep、tail、left_tail、right_tail、trade_when等)", operators: ["filter", "clamp", "keep", "tail", "left_tail", "right_tail", "trade_when"] }, { id: 12, name: "分组聚合和统计摘要", description: "在每个组内(如行业、部门、国家)聚合或摘要(例如,均值、总和、标准差、最小值、最大值、中位数)。每个股票根据其组成员资格接收组级值。", operators: ["group_mean", "group_sum", "group_std_dev", "group_min", "group_max", "group_median", "group_count", "group_percentage", "group_extra"] }, { id: 13, name: "分组排名、缩放和归一化", description: "在每个组内进行排名、缩放或归一化(例如,每个股票的行业排名,在部门内缩放值)。每个股票在其组内同行中进行排名或缩放。", operators: ["group_rank", "group_scale", "group_zscore", "group_normalize"] }, { id: 14, name: "分组回归和中性化", description: "移除组级影响,在每个组内执行回归或正交化(例如,行业中性化,分组回归)。每个组独立处理。", operators: ["group_vector_neut", "group_vector_proj", "group_neutralize", "group_multi_regression"] }, { id: 15, name: "分组插补和回填", description: "使用同一组中其他股票的数据插补缺失值或回填(例如,用组均值或中位数填充NaN,group_backfill)。", operators: ["group_backfill"] } ]; // 显示类别弹出窗口 function showCategoryPopup(categoryName, event) { event.stopPropagation(); // 查找类别数据 const categoryData = operatorsData.find(cat => cat.name === categoryName); if (!categoryData) { console.log('未找到类别:', categoryName); return; } // 填充弹出窗口内容 categoryPopupTitle.textContent = categoryData.name; categoryPopupDescription.textContent = categoryData.description; categoryPopupOperatorsTitle.textContent = `可用操作符 (${categoryData.operators.length}):`; const operatorsHtml = categoryData.operators.map(op => `${op}` ).join(''); categoryPopupOperators.innerHTML = operatorsHtml; // 将弹出窗口定位在点击元素附近 const rect = event.target.getBoundingClientRect(); const popup = categoryPopup; popup.style.display = 'block'; // 计算位置 let left = rect.left + window.scrollX; let top = rect.bottom + window.scrollY + 5; // 如果弹出窗口会超出屏幕则调整 const popupRect = popup.getBoundingClientRect(); if (left + popupRect.width > window.innerWidth) { left = window.innerWidth - popupRect.width - 20; } if (top + popupRect.height > window.innerHeight + window.scrollY) { top = rect.top + window.scrollY - popupRect.height - 5; } popup.style.left = left + 'px'; popup.style.top = top + 'px'; } // 隐藏类别弹出窗口 function hideCategoryPopup() { categoryPopup.style.display = 'none'; } // 使函数在onclick处理程序中全局可用 window.showCategoryPopup = showCategoryPopup; window.hideCategoryPopup = hideCategoryPopup; // 保存对话状态的函数 function saveConversationState() { sessionStorage.setItem('featureEngConversationHistory', JSON.stringify(conversationHistory)); sessionStorage.setItem('featureEngCurrentStep', currentStep.toString()); sessionStorage.setItem('featureEngPipelineSteps', JSON.stringify(pipelineSteps)); sessionStorage.setItem('featureEngCurrentOptions', JSON.stringify(currentOptions)); sessionStorage.setItem('featureEngCurrentDataState', currentDataState); console.log('对话状态已保存到sessionStorage'); }