idea_house.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. // Idea House JavaScript - 处理数据字段选择和Coze API处理
  2. // 全局变量
  3. let dataFields = [];
  4. let filteredDataFields = [];
  5. let selectedFields = new Map(); // 字段ID -> 描述的映射
  6. // 筛选状态变量
  7. let columnFilters = {
  8. id: '',
  9. description: '',
  10. type: '',
  11. coverage: { min: null, max: null },
  12. userCount: null,
  13. alphaCount: null
  14. };
  15. let sortColumn = null;
  16. let sortOrder = 'asc';
  17. // DOM加载完成后初始化
  18. document.addEventListener('DOMContentLoaded', function() {
  19. // 设置事件监听器
  20. document.getElementById('loadDataFieldsBtn').addEventListener('click', loadDataFields);
  21. document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection);
  22. document.getElementById('processFieldsBtn').addEventListener('click', processSelectedFields);
  23. // 设置API令牌切换按钮
  24. const toggleBtn = document.getElementById('toggleApiTokenBtn');
  25. const tokenInput = document.getElementById('cozeApiTokenInput');
  26. toggleBtn.addEventListener('click', function() {
  27. if (tokenInput.type === 'password') {
  28. tokenInput.type = 'text';
  29. toggleBtn.textContent = '隐藏';
  30. } else {
  31. tokenInput.type = 'password';
  32. toggleBtn.textContent = '显示';
  33. }
  34. });
  35. // 加载保存的配置
  36. loadSavedCozeConfig();
  37. // 设置保存配置按钮
  38. document.getElementById('saveCozeConfigBtn').addEventListener('click', saveCozeConfig);
  39. // 设置清除配置按钮
  40. document.getElementById('clearCozeConfigBtn').addEventListener('click', clearCozeConfig);
  41. // 设置筛选事件监听器
  42. setupFilterEventListeners();
  43. });
  44. // 从BRAIN API加载数据字段
  45. async function loadDataFields() {
  46. const region = document.getElementById('regionInput').value;
  47. const delay = document.getElementById('delayInput').value;
  48. const universe = document.getElementById('universeInput').value;
  49. const datasetId = document.getElementById('datasetInput').value;
  50. // 显示加载状态
  51. document.getElementById('dataFieldsStats').textContent = '正在加载数据字段...';
  52. document.getElementById('tableContainer').style.display = 'none';
  53. try {
  54. // 从localStorage或cookie获取会话ID
  55. const sessionId = localStorage.getItem('brain_session_id');
  56. if (!sessionId) {
  57. alert('请先从主页面连接到BRAIN');
  58. return;
  59. }
  60. // 调用代理端点
  61. const params = new URLSearchParams({
  62. region: region,
  63. delay: delay,
  64. universe: universe,
  65. dataset_id: datasetId
  66. });
  67. // 记录API调用
  68. console.log('🚀 正在调用API获取数据字段和数据集描述');
  69. console.log('📋 参数:', {
  70. region: region,
  71. delay: delay,
  72. universe: universe,
  73. dataset_id: datasetId
  74. });
  75. // 并行获取数据字段和数据集描述
  76. const [dataFieldsResponse, descriptionResponse] = await Promise.all([
  77. fetch(`/idea-house/api/get-datafields-proxy?${params}`, {
  78. method: 'GET',
  79. headers: {
  80. 'Session-ID': sessionId
  81. }
  82. }),
  83. fetch(`/idea-house/api/get-dataset-description?${params}`, {
  84. method: 'GET',
  85. headers: {
  86. 'Session-ID': sessionId
  87. }
  88. })
  89. ]);
  90. console.log('📥 收到响应:');
  91. console.log(' 数据字段状态:', dataFieldsResponse.status);
  92. console.log(' 数据集描述状态:', descriptionResponse.status);
  93. if (!dataFieldsResponse.ok) {
  94. const errorData = await dataFieldsResponse.json();
  95. throw new Error(errorData.error || '获取数据字段失败');
  96. }
  97. dataFields = await dataFieldsResponse.json();
  98. // 获取数据集描述(如果可用)
  99. let datasetDescription = '';
  100. if (descriptionResponse.ok) {
  101. console.log('✅ 数据集描述响应正常,正在解析JSON...');
  102. const descriptionData = await descriptionResponse.json();
  103. console.log('📄 描述数据:', descriptionData);
  104. if (descriptionData.success) {
  105. datasetDescription = descriptionData.description;
  106. // 将其存储在全局中供后续使用
  107. window.currentDatasetDescription = datasetDescription;
  108. console.log('✅ 数据集描述已存储:', datasetDescription);
  109. } else {
  110. console.log('⚠️ 描述响应success=false');
  111. }
  112. } else {
  113. console.log('❌ 数据集描述响应不正常:', descriptionResponse.status);
  114. try {
  115. const errorData = await descriptionResponse.json();
  116. console.log('❌ 错误详情:', errorData);
  117. } catch (e) {
  118. console.log('❌ 无法解析错误响应');
  119. }
  120. }
  121. // 更新统计信息
  122. document.getElementById('dataFieldsStats').textContent = `已加载 ${dataFields.length} 个数据字段`;
  123. // 使用数据集描述填充表格
  124. populateDataFieldsTable(datasetDescription);
  125. // 显示表格
  126. document.getElementById('tableContainer').style.display = 'block';
  127. } catch (error) {
  128. console.error('加载数据字段失败:', error);
  129. document.getElementById('dataFieldsStats').textContent = `错误: ${error.message}`;
  130. alert(`加载数据字段失败: ${error.message}`);
  131. }
  132. }
  133. // 使用筛选和排序填充数据字段表格
  134. function populateDataFieldsTable(datasetDescription) {
  135. console.log('📊 populateDataFieldsTable 被调用,描述:', datasetDescription);
  136. const tableBody = document.getElementById('dataFieldsTableBody');
  137. const highCoverageFilter = document.getElementById('filterHighCoverage')?.checked || false;
  138. const popularFilter = document.getElementById('filterPopular')?.checked || false;
  139. const matrixOnlyFilter = document.getElementById('filterMatrixOnly')?.checked || false;
  140. // 显示数据集描述(如果可用)
  141. if (datasetDescription) {
  142. console.log('🎯 显示传入的数据集描述');
  143. displayDatasetDescription(datasetDescription);
  144. } else if (window.currentDatasetDescription) {
  145. console.log('🎯 显示已存储的数据集描述');
  146. displayDatasetDescription(window.currentDatasetDescription);
  147. } else {
  148. console.log('⚠️ 无数据集描述可用');
  149. }
  150. // 应用筛选器
  151. filteredDataFields = dataFields.filter(field => {
  152. // 列特定筛选器
  153. // ID筛选器
  154. if (columnFilters.id && !field.id.toLowerCase().includes(columnFilters.id.toLowerCase())) {
  155. return false;
  156. }
  157. // 描述筛选器
  158. if (columnFilters.description && !field.description.toLowerCase().includes(columnFilters.description.toLowerCase())) {
  159. return false;
  160. }
  161. // 类型筛选器
  162. if (columnFilters.type && field.type !== columnFilters.type) {
  163. return false;
  164. }
  165. // 覆盖率范围筛选器
  166. if (columnFilters.coverage.min !== null && field.coverage * 100 < columnFilters.coverage.min) {
  167. return false;
  168. }
  169. if (columnFilters.coverage.max !== null && field.coverage * 100 > columnFilters.coverage.max) {
  170. return false;
  171. }
  172. // 用户数量筛选器
  173. if (columnFilters.userCount !== null && field.userCount < columnFilters.userCount) {
  174. return false;
  175. }
  176. // Alpha数量筛选器
  177. if (columnFilters.alphaCount !== null && field.alphaCount < columnFilters.alphaCount) {
  178. return false;
  179. }
  180. // 高覆盖率筛选器
  181. if (highCoverageFilter && field.coverage < 0.9) {
  182. return false;
  183. }
  184. // 流行度筛选器
  185. if (popularFilter && field.userCount < 1000) {
  186. return false;
  187. }
  188. // 矩阵类型筛选器
  189. if (matrixOnlyFilter && field.type !== 'MATRIX') {
  190. return false;
  191. }
  192. return true;
  193. });
  194. // 对筛选后的数据字段排序
  195. if (sortColumn) {
  196. filteredDataFields.sort((a, b) => {
  197. let aVal = a[sortColumn];
  198. let bVal = b[sortColumn];
  199. // 处理数值
  200. if (sortColumn === 'coverage' || sortColumn === 'userCount' || sortColumn === 'alphaCount') {
  201. aVal = Number(aVal);
  202. bVal = Number(bVal);
  203. } else {
  204. // 字符串比较
  205. aVal = String(aVal).toLowerCase();
  206. bVal = String(bVal).toLowerCase();
  207. }
  208. if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;
  209. if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;
  210. return 0;
  211. });
  212. }
  213. // 清空表格
  214. tableBody.innerHTML = '';
  215. if (filteredDataFields.length === 0) {
  216. tableBody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: #666; padding: 40px;">未找到符合筛选条件的数据字段</td></tr>';
  217. updateDataFieldsStats();
  218. return;
  219. }
  220. // 创建表格行
  221. filteredDataFields.forEach(field => {
  222. const row = document.createElement('tr');
  223. row.dataset.fieldId = field.id;
  224. row.dataset.fieldDescription = field.description;
  225. if (selectedFields.has(field.id)) {
  226. row.classList.add('selected');
  227. }
  228. row.innerHTML = `
  229. <td>
  230. <input type="checkbox" class="field-checkbox" data-field-id="${field.id}" data-field-description="${field.description}" ${selectedFields.has(field.id) ? 'checked' : ''}>
  231. </td>
  232. <td><span class="data-field-id">${field.id}</span></td>
  233. <td><span class="data-field-description">${field.description}</span></td>
  234. <td><span class="data-field-type">${field.type || 'N/A'}</span></td>
  235. <td><span class="data-field-coverage">${field.coverage ? (field.coverage * 100).toFixed(1) + '%' : 'N/A'}</span></td>
  236. <td><span class="data-field-count">${field.userCount ? field.userCount.toLocaleString() : 'N/A'}</span></td>
  237. <td><span class="data-field-count">${field.alphaCount ? field.alphaCount.toLocaleString() : 'N/A'}</span></td>
  238. `;
  239. // 为行添加点击处理程序
  240. row.addEventListener('click', function(e) {
  241. if (e.target.type !== 'checkbox') {
  242. const checkbox = row.querySelector('.field-checkbox');
  243. checkbox.checked = !checkbox.checked;
  244. handleFieldSelection(checkbox);
  245. }
  246. });
  247. // 为复选框添加变更处理程序
  248. const checkbox = row.querySelector('.field-checkbox');
  249. checkbox.addEventListener('change', function() {
  250. handleFieldSelection(this);
  251. });
  252. tableBody.appendChild(row);
  253. });
  254. // 更新统计信息并填充类型筛选器
  255. updateDataFieldsStats();
  256. populateTypeFilter();
  257. updateSelectAllCheckbox();
  258. }
  259. // 处理字段选择
  260. function handleFieldSelection(checkbox) {
  261. const fieldId = checkbox.dataset.fieldId;
  262. const fieldDescription = checkbox.dataset.fieldDescription;
  263. const row = checkbox.closest('tr');
  264. if (checkbox.checked) {
  265. selectedFields.set(fieldId, fieldDescription);
  266. row.classList.add('selected');
  267. } else {
  268. selectedFields.delete(fieldId);
  269. row.classList.remove('selected');
  270. }
  271. updateSelectedFieldsDisplay();
  272. updateDataFieldsStats();
  273. updateSelectAllCheckbox();
  274. }
  275. // 更新选中字段的显示
  276. function updateSelectedFieldsDisplay() {
  277. const selectedFieldsList = document.getElementById('selectedFieldsList');
  278. const selectedFieldsSection = document.getElementById('selectedFieldsSection');
  279. if (selectedFields.size === 0) {
  280. selectedFieldsSection.style.display = 'none';
  281. return;
  282. }
  283. selectedFieldsSection.style.display = 'block';
  284. selectedFieldsList.innerHTML = '';
  285. selectedFields.forEach((description, fieldId) => {
  286. const fieldItem = document.createElement('div');
  287. fieldItem.className = 'selected-field-item';
  288. fieldItem.textContent = `${fieldId}: ${description}`;
  289. selectedFieldsList.appendChild(fieldItem);
  290. });
  291. }
  292. // 清除所有选择
  293. function clearSelection() {
  294. selectedFields.clear();
  295. // 取消选中所有复选框并移除选中类
  296. document.querySelectorAll('.field-checkbox').forEach(checkbox => {
  297. checkbox.checked = false;
  298. checkbox.closest('tr').classList.remove('selected');
  299. });
  300. updateSelectedFieldsDisplay();
  301. updateDataFieldsStats();
  302. updateSelectAllCheckbox();
  303. }
  304. // 使用Coze API处理选中的字段
  305. async function processSelectedFields() {
  306. if (selectedFields.size === 0) {
  307. alert('请至少选择一个字段');
  308. return;
  309. }
  310. // 显示加载覆盖层,包含Coze API特定消息
  311. const loadingOverlay = document.getElementById('loadingOverlay');
  312. const loadingContent = loadingOverlay.querySelector('.loading-content');
  313. loadingContent.innerHTML = `
  314. <h3>🚀 正在向Coze API发送请求...</h3>
  315. <p>正在通过Coze工作流处理 ${selectedFields.size} 个选中的字段</p>
  316. <p style="font-size: 14px; color: #666; margin-top: 10px;">
  317. 📡 正在连接到Coze API...<br>
  318. ⚙️ 正在运行工作流分析...<br>
  319. 📊 正在生成洞察...
  320. </p>
  321. `;
  322. loadingOverlay.style.display = 'flex';
  323. try {
  324. // 准备所需格式的数据 {"id":"description"}
  325. const selectedFieldsObject = {};
  326. selectedFields.forEach((description, fieldId) => {
  327. selectedFieldsObject[fieldId] = description;
  328. });
  329. // 获取Coze API配置
  330. const cozeApiToken = document.getElementById('cozeApiTokenInput').value;
  331. const workflowId = document.getElementById('workflowIdInput').value;
  332. // 验证输入
  333. if (!cozeApiToken) {
  334. alert('请输入Coze API令牌');
  335. document.getElementById('loadingOverlay').style.display = 'none';
  336. return;
  337. }
  338. if (!workflowId) {
  339. alert('请输入工作流ID');
  340. document.getElementById('loadingOverlay').style.display = 'none';
  341. return;
  342. }
  343. // 更新加载消息以显示API调用正在进行
  344. loadingContent.innerHTML = `
  345. <h3>📡 Coze API请求进行中...</h3>
  346. <p>工作流ID: ${workflowId}</p>
  347. <p>选中的字段: ${Object.keys(selectedFieldsObject).join(', ')}</p>
  348. <p style="font-size: 14px; color: #4caf50; margin-top: 10px;">
  349. ✅ API凭据已验证<br>
  350. 🔄 正在向Coze服务器发送请求...<br>
  351. ⏳ 请等待响应...
  352. </p>
  353. `;
  354. console.log('🚀 开始Coze API请求...');
  355. console.log('📋 选中的字段:', selectedFieldsObject);
  356. console.log('🔑 使用以以下结尾的API令牌:', cozeApiToken.slice(-10));
  357. console.log('⚙️ 工作流ID:', workflowId);
  358. // 调用处理端点
  359. const response = await fetch('/idea-house/api/process-fields', {
  360. method: 'POST',
  361. headers: {
  362. 'Content-Type': 'application/json'
  363. },
  364. body: JSON.stringify({
  365. selected_fields: selectedFieldsObject,
  366. coze_api_token: cozeApiToken,
  367. workflow_id: workflowId,
  368. dataset_description: window.currentDatasetDescription || ''
  369. })
  370. });
  371. console.log('📡 收到服务器响应');
  372. const result = await response.json();
  373. if (!response.ok) {
  374. console.error('❌ Coze API请求失败:', result.error);
  375. throw new Error(result.error || '处理字段失败');
  376. }
  377. console.log('✅ Coze API请求成功!');
  378. console.log('📊 结果:', result);
  379. // 更新加载消息以显示成功
  380. loadingContent.innerHTML = `
  381. <h3>✅ 收到Coze API响应!</h3>
  382. <p style="color: #4caf50;">已成功通过工作流处理</p>
  383. <p style="font-size: 14px; margin-top: 10px;">
  384. 📥 收到来自Coze的响应<br>
  385. 🎉 正在格式化结果...
  386. </p>
  387. `;
  388. // 短暂延迟以显示成功消息
  389. await new Promise(resolve => setTimeout(resolve, 1000));
  390. // 显示结果
  391. displayResults(result);
  392. } catch (error) {
  393. console.error('💥 通过Coze API处理字段失败:', error);
  394. alert(`通过Coze API处理字段失败: ${error.message}`);
  395. } finally {
  396. // 隐藏加载覆盖层
  397. document.getElementById('loadingOverlay').style.display = 'none';
  398. // 为下次使用重置加载内容
  399. loadingContent.innerHTML = `
  400. <h3>处理中...</h3>
  401. <p>请等待我们分析您选中的字段...</p>
  402. `;
  403. }
  404. }
  405. // 以markdown格式显示结果
  406. function displayResults(result) {
  407. const resultsSection = document.getElementById('resultsSection');
  408. const resultsContent = document.getElementById('resultsContent');
  409. // 显示结果部分
  410. resultsSection.style.display = 'block';
  411. // 将结果格式化为markdown - 简化版本
  412. let markdown = '# 分析结果\n\n';
  413. // 添加选中字段部分
  414. markdown += '## 选中的字段\n\n';
  415. Object.entries(result.selected_fields).forEach(([fieldId, description]) => {
  416. markdown += `- **${fieldId}**: ${description}\n`;
  417. });
  418. markdown += '\n';
  419. // 添加输出部分 - 仅显示实际分析输出
  420. markdown += '## 分析输出\n\n';
  421. if (result.output) {
  422. // 如果输出已经是格式化文本,直接使用
  423. if (typeof result.output === 'string') {
  424. markdown += result.output;
  425. } else {
  426. // 如果是对象,尝试美观地显示
  427. markdown += '```json\n';
  428. markdown += JSON.stringify(result.output, null, 2);
  429. markdown += '\n```\n';
  430. }
  431. } else {
  432. markdown += '_无输出数据可用_';
  433. }
  434. // 将markdown渲染为HTML
  435. resultsContent.innerHTML = renderMarkdown(markdown);
  436. // 滚动到结果部分
  437. resultsSection.scrollIntoView({ behavior: 'smooth' });
  438. }
  439. // 辅助函数格式化markdown(可选增强功能)
  440. function renderMarkdown(markdown) {
  441. // 这是一个改进的markdown渲染器,能更好地处理列表
  442. let html = markdown;
  443. // 首先,转义HTML以防止XSS
  444. html = html.replace(/&/g, '&amp;')
  445. .replace(/</g, '&lt;')
  446. .replace(/>/g, '&gt;');
  447. // 代码块(必须在行内代码之前)
  448. html = html.replace(/```([\s\S]*?)```/g, function(match, code) {
  449. return '<pre><code>' + code.trim() + '</code></pre>';
  450. });
  451. // 标题
  452. html = html.replace(/^#### (.*$)/gim, '<h4>$1</h4>');
  453. html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
  454. html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
  455. html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
  456. // 粗体(必须在斜体之前)
  457. html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  458. // 斜体
  459. html = html.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
  460. // 行内代码
  461. html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
  462. // 更仔细地处理列表
  463. // 为了更好的列表处理,分割成行
  464. const lines = html.split('\n');
  465. let inList = false;
  466. let processedLines = [];
  467. for (let i = 0; i < lines.length; i++) {
  468. let line = lines[i];
  469. // 检查行是否是列表项
  470. if (line.match(/^[\*\-\+] /)) {
  471. // 用适当的HTML替换列表标记
  472. line = line.replace(/^[\*\-\+] (.*)$/, '<li>$1</li>');
  473. // 如果不在列表中,开始一个列表
  474. if (!inList) {
  475. processedLines.push('<ul>');
  476. inList = true;
  477. }
  478. processedLines.push(line);
  479. } else if (line.match(/^\d+\. /)) {
  480. // 有序列表
  481. line = line.replace(/^\d+\. (.*)$/, '<li>$1</li>');
  482. // 如果不在列表中或前一个是无序列表,开始有序列表
  483. if (!inList || (i > 0 && lines[i-1].match(/^[\*\-\+] /))) {
  484. if (inList) processedLines.push('</ul>');
  485. processedLines.push('<ol>');
  486. inList = true;
  487. }
  488. processedLines.push(line);
  489. } else {
  490. // 不是列表项
  491. if (inList) {
  492. // 关闭列表
  493. if (i > 0 && lines[i-1].match(/^\d+\. /)) {
  494. processedLines.push('</ol>');
  495. } else {
  496. processedLines.push('</ul>');
  497. }
  498. inList = false;
  499. }
  500. processedLines.push(line);
  501. }
  502. }
  503. // 关闭任何剩余的列表
  504. if (inList) {
  505. if (lines[lines.length - 1].match(/^\d+\. /)) {
  506. processedLines.push('</ol>');
  507. } else {
  508. processedLines.push('</ul>');
  509. }
  510. }
  511. html = processedLines.join('\n');
  512. // 换行 - 将双换行符转换为段落
  513. html = html.replace(/\n\n/g, '</p><p>');
  514. html = '<p>' + html + '</p>';
  515. // 清理空段落
  516. html = html.replace(/<p>\s*<\/p>/g, '');
  517. html = html.replace(/<p>(<h[1-6]>)/g, '$1');
  518. html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
  519. html = html.replace(/<p>(<ul>|<ol>|<pre>)/g, '$1');
  520. html = html.replace(/(<\/ul>|<\/ol>|<\/pre>)<\/p>/g, '$1');
  521. // 段落内的单换行符
  522. html = html.replace(/\n/g, '<br>');
  523. return html;
  524. }
  525. // 将Coze配置保存到localStorage
  526. function saveCozeConfig() {
  527. const cozeApiToken = document.getElementById('cozeApiTokenInput').value;
  528. const workflowId = document.getElementById('workflowIdInput').value;
  529. // 保存到localStorage
  530. localStorage.setItem('coze_api_token', cozeApiToken);
  531. localStorage.setItem('coze_workflow_id', workflowId);
  532. // 显示成功消息
  533. const messageElement = document.getElementById('saveConfigMessage');
  534. messageElement.style.display = 'inline';
  535. // 3秒后隐藏消息
  536. setTimeout(() => {
  537. messageElement.style.display = 'none';
  538. }, 3000);
  539. }
  540. // 从localStorage加载保存的Coze配置
  541. function loadSavedCozeConfig() {
  542. const savedToken = localStorage.getItem('coze_api_token');
  543. const savedWorkflowId = localStorage.getItem('coze_workflow_id');
  544. if (savedToken) {
  545. document.getElementById('cozeApiTokenInput').value = savedToken;
  546. }
  547. if (savedWorkflowId) {
  548. document.getElementById('workflowIdInput').value = savedWorkflowId;
  549. }
  550. }
  551. // 清除Coze配置并重置为默认值
  552. function clearCozeConfig() {
  553. // 从localStorage移除
  554. localStorage.removeItem('coze_api_token');
  555. localStorage.removeItem('coze_workflow_id');
  556. // 重置为默认值
  557. document.getElementById('cozeApiTokenInput').value = 'pat_OCxUpnmL7hCvUxEWwcKL5XwUOdoiA3eWLzwY6L8W9sQVN1saJnoMrDNyhFhEn63l';
  558. document.getElementById('workflowIdInput').value = '7522912027267956786';
  559. // 显示消息
  560. const messageElement = document.getElementById('saveConfigMessage');
  561. messageElement.textContent = '配置已重置为默认值!';
  562. messageElement.style.color = '#ff9800';
  563. messageElement.style.display = 'inline';
  564. // 3秒后隐藏消息
  565. setTimeout(() => {
  566. messageElement.style.display = 'none';
  567. messageElement.textContent = '配置已保存!';
  568. messageElement.style.color = '#4caf50';
  569. }, 3000);
  570. }
  571. // 设置筛选事件监听器
  572. function setupFilterEventListeners() {
  573. // 快速筛选复选框
  574. const highCoverageFilter = document.getElementById('filterHighCoverage');
  575. const popularFilter = document.getElementById('filterPopular');
  576. const matrixOnlyFilter = document.getElementById('filterMatrixOnly');
  577. // 筛选操作按钮
  578. const selectAllBtn = document.getElementById('selectAllFiltered');
  579. const clearAllBtn = document.getElementById('clearAllSelected');
  580. const selectAllCheckbox = document.getElementById('selectAllCheckbox');
  581. // 设置快速筛选监听器
  582. if (highCoverageFilter) highCoverageFilter.onchange = () => populateDataFieldsTable();
  583. if (popularFilter) popularFilter.onchange = () => populateDataFieldsTable();
  584. if (matrixOnlyFilter) matrixOnlyFilter.onchange = () => populateDataFieldsTable();
  585. // 设置操作按钮监听器
  586. if (selectAllBtn) selectAllBtn.onclick = selectAllFilteredFields;
  587. if (clearAllBtn) clearAllBtn.onclick = clearAllSelectedFields;
  588. if (selectAllCheckbox) {
  589. selectAllCheckbox.onclick = (e) => {
  590. e.stopPropagation();
  591. if (selectAllCheckbox.checked) {
  592. selectAllFilteredFields();
  593. } else {
  594. clearAllFilteredFields();
  595. }
  596. };
  597. }
  598. // 列筛选监听器
  599. document.querySelectorAll('.column-filter').forEach(filter => {
  600. filter.addEventListener('input', (e) => {
  601. const column = e.target.dataset.column;
  602. const value = e.target.value;
  603. if (column === 'userCount' || column === 'alphaCount') {
  604. columnFilters[column] = value ? parseInt(value) : null;
  605. } else {
  606. columnFilters[column] = value;
  607. }
  608. // 添加/移除活动类
  609. if (value) {
  610. e.target.classList.add('active');
  611. } else {
  612. e.target.classList.remove('active');
  613. }
  614. populateDataFieldsTable();
  615. });
  616. });
  617. // 覆盖率范围筛选器
  618. document.querySelectorAll('.column-filter-min, .column-filter-max').forEach(filter => {
  619. filter.addEventListener('input', (e) => {
  620. const isMin = e.target.classList.contains('column-filter-min');
  621. const value = e.target.value;
  622. if (isMin) {
  623. columnFilters.coverage.min = value ? parseFloat(value) : null;
  624. } else {
  625. columnFilters.coverage.max = value ? parseFloat(value) : null;
  626. }
  627. // 添加/移除活动类
  628. const minInput = e.target.parentElement.querySelector('.column-filter-min');
  629. const maxInput = e.target.parentElement.querySelector('.column-filter-max');
  630. if (minInput.value || maxInput.value) {
  631. minInput.classList.add('active');
  632. maxInput.classList.add('active');
  633. } else {
  634. minInput.classList.remove('active');
  635. maxInput.classList.remove('active');
  636. }
  637. populateDataFieldsTable();
  638. });
  639. });
  640. // 排序按钮监听器
  641. document.querySelectorAll('.sort-btn').forEach(btn => {
  642. btn.addEventListener('click', (e) => {
  643. const column = e.target.dataset.column;
  644. // 重置所有其他排序按钮
  645. document.querySelectorAll('.sort-btn').forEach(b => {
  646. if (b !== e.target) {
  647. b.classList.remove('asc', 'desc');
  648. b.dataset.order = 'asc';
  649. }
  650. });
  651. // 切换排序顺序
  652. if (sortColumn === column) {
  653. sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
  654. } else {
  655. sortColumn = column;
  656. sortOrder = 'asc';
  657. }
  658. e.target.dataset.order = sortOrder;
  659. e.target.classList.remove('asc', 'desc');
  660. e.target.classList.add(sortOrder);
  661. populateDataFieldsTable();
  662. });
  663. });
  664. }
  665. // 更新数据字段统计信息
  666. function updateDataFieldsStats() {
  667. const dataFieldsCountEl = document.getElementById('dataFieldsCount');
  668. const filteredCountEl = document.getElementById('filteredCount');
  669. const selectedCountEl = document.getElementById('selectedFieldsCount');
  670. if (dataFieldsCountEl) dataFieldsCountEl.textContent = `已加载 ${dataFields.length} 个字段`;
  671. if (filteredCountEl) filteredCountEl.textContent = `${filteredDataFields.length} 个已筛选`;
  672. if (selectedCountEl) selectedCountEl.textContent = `${selectedFields.size} 个已选中`;
  673. }
  674. // 填充类型筛选下拉菜单
  675. function populateTypeFilter() {
  676. const typeFilter = document.getElementById('typeFilter');
  677. if (!typeFilter) return;
  678. // 从当前数据字段获取唯一类型
  679. const uniqueTypes = [...new Set(dataFields.map(field => field.type))].sort();
  680. // 清除现有选项,除了"所有类型"
  681. typeFilter.innerHTML = '<option value="">所有类型</option>';
  682. uniqueTypes.forEach(type => {
  683. const option = document.createElement('option');
  684. option.value = type;
  685. option.textContent = type;
  686. typeFilter.appendChild(option);
  687. });
  688. // 如果存在选中的值,恢复它
  689. if (columnFilters.type && uniqueTypes.includes(columnFilters.type)) {
  690. typeFilter.value = columnFilters.type;
  691. }
  692. }
  693. // 选中所有筛选后的字段
  694. function selectAllFilteredFields() {
  695. filteredDataFields.forEach(field => {
  696. selectedFields.set(field.id, field.description);
  697. const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
  698. if (row) {
  699. const checkbox = row.querySelector('.field-checkbox');
  700. checkbox.checked = true;
  701. row.classList.add('selected');
  702. }
  703. });
  704. updateSelectedFieldsDisplay();
  705. updateDataFieldsStats();
  706. updateSelectAllCheckbox();
  707. }
  708. // 清除所有选中的字段
  709. function clearAllSelectedFields() {
  710. selectedFields.clear();
  711. // 更新所有复选框
  712. document.querySelectorAll('.field-checkbox').forEach(checkbox => {
  713. checkbox.checked = false;
  714. checkbox.closest('tr').classList.remove('selected');
  715. });
  716. updateSelectedFieldsDisplay();
  717. updateDataFieldsStats();
  718. updateSelectAllCheckbox();
  719. }
  720. // 仅清除筛选后的字段
  721. function clearAllFilteredFields() {
  722. filteredDataFields.forEach(field => {
  723. selectedFields.delete(field.id);
  724. const row = document.querySelector(`tr[data-field-id="${field.id}"]`);
  725. if (row) {
  726. const checkbox = row.querySelector('.field-checkbox');
  727. checkbox.checked = false;
  728. row.classList.remove('selected');
  729. }
  730. });
  731. updateSelectedFieldsDisplay();
  732. updateDataFieldsStats();
  733. updateSelectAllCheckbox();
  734. }
  735. // 更新全选复选框状态
  736. function updateSelectAllCheckbox() {
  737. const selectAllCheckbox = document.getElementById('selectAllCheckbox');
  738. if (!selectAllCheckbox) return;
  739. const allFilteredSelected = filteredDataFields.length > 0 &&
  740. filteredDataFields.every(field => selectedFields.has(field.id));
  741. selectAllCheckbox.checked = allFilteredSelected;
  742. selectAllCheckbox.indeterminate = !allFilteredSelected &&
  743. filteredDataFields.some(field => selectedFields.has(field.id));
  744. }
  745. // 显示数据集描述
  746. function displayDatasetDescription(description) {
  747. console.log('🎨 显示数据集描述:', description);
  748. // 检查数据集描述元素是否已存在
  749. let descriptionElement = document.getElementById('datasetDescription');
  750. if (!descriptionElement) {
  751. console.log('📌 创建新的数据集描述元素');
  752. // 如果元素不存在则创建它
  753. const tableContainer = document.getElementById('tableContainer');
  754. const dataFieldsControls = tableContainer.querySelector('.data-fields-controls');
  755. // 为数据集描述创建一个新的div
  756. descriptionElement = document.createElement('div');
  757. descriptionElement.id = 'datasetDescription';
  758. descriptionElement.className = 'dataset-description';
  759. descriptionElement.style.cssText = `
  760. padding: 15px;
  761. background: #e8f5e9;
  762. border: 1px solid #4caf50;
  763. border-radius: 4px;
  764. margin-bottom: 15px;
  765. font-size: 14px;
  766. line-height: 1.5;
  767. `;
  768. // 将其插入到控件之前
  769. tableContainer.insertBefore(descriptionElement, dataFieldsControls);
  770. console.log('✅ 数据集描述元素已创建并插入');
  771. } else {
  772. console.log('📌 使用现有的数据集描述元素');
  773. }
  774. // 更新内容
  775. descriptionElement.innerHTML = `
  776. <strong>数据集描述:</strong><br>
  777. ${description}
  778. `;
  779. console.log('✅ 数据集描述内容已更新');
  780. }