|
|
@@ -0,0 +1,507 @@
|
|
|
+/**
|
|
|
+ * 专业性审查功能模块
|
|
|
+ */
|
|
|
+
|
|
|
+// ==================== 功能切换 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 切换功能Tab
|
|
|
+ */
|
|
|
+function switchFunction(functionType) {
|
|
|
+ // 切换Tab激活状态
|
|
|
+ document.querySelectorAll('.function-tab').forEach(tab => {
|
|
|
+ tab.classList.remove('active');
|
|
|
+ });
|
|
|
+ document.querySelector(`[data-function="${functionType}"]`).classList.add('active');
|
|
|
+
|
|
|
+ // 获取元素
|
|
|
+ const sectionTitle = document.getElementById('sectionTitle');
|
|
|
+ const testInput = document.getElementById('testInput');
|
|
|
+ const reviewTypeSelector = document.getElementById('reviewTypeSelector');
|
|
|
+ const runRagBtn = document.getElementById('runRagBtn');
|
|
|
+ const runProfessionalBtn = document.getElementById('runProfessionalBtn');
|
|
|
+ const loadSampleBtn = document.getElementById('loadSampleBtn');
|
|
|
+
|
|
|
+ const pipelineOverview = document.getElementById('pipelineOverview');
|
|
|
+ const pipelineFlow = document.getElementById('pipelineFlow');
|
|
|
+ const detailPanel = document.getElementById('detailPanel');
|
|
|
+ const stagesDetail = document.getElementById('stagesDetail');
|
|
|
+ const professionalResults = document.getElementById('professionalResults');
|
|
|
+
|
|
|
+ if (functionType === 'rag') {
|
|
|
+ // 切换到RAG模式
|
|
|
+ sectionTitle.textContent = '🚀 RAG链路测试';
|
|
|
+ testInput.placeholder = '输入测试文本,点击执行RAG检索...\n\n示例:主要部件说明\n1、主梁总成\n主梁总成由主梁和导梁构成。主梁单节长12m,共7节,每节重10.87t...';
|
|
|
+
|
|
|
+ // 显示/隐藏按钮
|
|
|
+ runRagBtn.style.display = 'inline-block';
|
|
|
+ runProfessionalBtn.style.display = 'none';
|
|
|
+ loadSampleBtn.style.display = 'inline-block';
|
|
|
+ reviewTypeSelector.style.display = 'none';
|
|
|
+
|
|
|
+ // 隐藏专业性审查结果
|
|
|
+ professionalResults.style.display = 'none';
|
|
|
+
|
|
|
+ // 如果有RAG数据,显示RAG相关面板
|
|
|
+ if (window.pipelineData) {
|
|
|
+ pipelineOverview.style.display = 'block';
|
|
|
+ pipelineFlow.style.display = 'block';
|
|
|
+ detailPanel.style.display = 'block';
|
|
|
+ stagesDetail.style.display = 'block';
|
|
|
+ }
|
|
|
+ } else if (functionType === 'professional') {
|
|
|
+ // 切换到专业性审查模式
|
|
|
+ sectionTitle.textContent = '🎯 专业性审查完整测试';
|
|
|
+ testInput.placeholder = '输入待审查内容...\n\n示例:\n二)架桥机安装施工\n1、安装准备\n(1)图纸审核...';
|
|
|
+
|
|
|
+ // 显示/隐藏按钮
|
|
|
+ runRagBtn.style.display = 'none';
|
|
|
+ runProfessionalBtn.style.display = 'inline-block';
|
|
|
+ loadSampleBtn.style.display = 'none';
|
|
|
+ reviewTypeSelector.style.display = 'flex';
|
|
|
+
|
|
|
+ // 隐藏RAG相关面板
|
|
|
+ pipelineOverview.style.display = 'none';
|
|
|
+ pipelineFlow.style.display = 'none';
|
|
|
+ detailPanel.style.display = 'none';
|
|
|
+ stagesDetail.style.display = 'none';
|
|
|
+
|
|
|
+ // 如果有专业性审查数据,显示结果面板
|
|
|
+ if (window.professionalReviewData) {
|
|
|
+ professionalResults.style.display = 'block';
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 执行专业性审查 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 执行专业性审查
|
|
|
+ */
|
|
|
+async function runProfessionalReview() {
|
|
|
+ const content = document.getElementById('testInput').value.trim();
|
|
|
+
|
|
|
+ if (!content) {
|
|
|
+ alert('请输入待审查内容');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!window.serverConnected) {
|
|
|
+ alert('服务器未连接,请检查服务是否启动');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取选中的审查类型
|
|
|
+ const checkType = document.querySelector('input[name="checkType"]:checked').value;
|
|
|
+
|
|
|
+ // 显示加载状态
|
|
|
+ const loadingOverlay = document.getElementById('loadingOverlay');
|
|
|
+ const loadingText = document.getElementById('loadingText');
|
|
|
+ const runBtn = document.getElementById('runProfessionalBtn');
|
|
|
+
|
|
|
+ loadingOverlay.style.display = 'flex';
|
|
|
+ loadingText.textContent = '正在执行专业性审查...';
|
|
|
+ runBtn.disabled = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const API_BASE = window.API_BASE || 'http://localhost:8765';
|
|
|
+ const response = await fetch(`${API_BASE}/api/professional_review`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ content: content,
|
|
|
+ check_type: checkType
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ console.log('📊 收到专业性审查响应:', data);
|
|
|
+
|
|
|
+ if (data.error) {
|
|
|
+ throw new Error(data.error);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存数据到全局变量
|
|
|
+ window.professionalReviewData = data;
|
|
|
+
|
|
|
+ // 显示结果
|
|
|
+ renderProfessionalResults(data);
|
|
|
+
|
|
|
+ // 显示结果面板
|
|
|
+ document.getElementById('professionalResults').style.display = 'block';
|
|
|
+
|
|
|
+ console.log('✅ 专业性审查完成:', data);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 专业性审查失败:', error);
|
|
|
+ alert(`专业性审查失败: ${error.message}`);
|
|
|
+ } finally {
|
|
|
+ loadingOverlay.style.display = 'none';
|
|
|
+ runBtn.disabled = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 结果渲染 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染专业性审查结果
|
|
|
+ */
|
|
|
+function renderProfessionalResults(data) {
|
|
|
+ // 渲染RAG召回摘要
|
|
|
+ renderRagSummary(data.rag_summary || {});
|
|
|
+
|
|
|
+ // 渲染审查结果列表
|
|
|
+ renderReviewResults(data.review_results || []);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染RAG召回摘要
|
|
|
+ */
|
|
|
+function renderRagSummary(summary) {
|
|
|
+ const ragSummary = document.getElementById('ragSummary');
|
|
|
+
|
|
|
+ const queryCount = summary.query_pairs_count || 0;
|
|
|
+ const entityCount = summary.entity_results_count || 0;
|
|
|
+ const reviewedCount = window.professionalReviewData ? window.professionalReviewData.total_entities_reviewed || 0 : 0;
|
|
|
+
|
|
|
+ ragSummary.innerHTML = `
|
|
|
+ <div class="summary-card">
|
|
|
+ <div class="summary-card-title">查询对数量</div>
|
|
|
+ <div class="summary-card-value">${queryCount}</div>
|
|
|
+ </div>
|
|
|
+ <div class="summary-card">
|
|
|
+ <div class="summary-card-title">召回实体数量</div>
|
|
|
+ <div class="summary-card-value">${entityCount}</div>
|
|
|
+ </div>
|
|
|
+ <div class="summary-card">
|
|
|
+ <div class="summary-card-title">审查实体数量</div>
|
|
|
+ <div class="summary-card-value">${reviewedCount}</div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染审查结果列表
|
|
|
+ */
|
|
|
+function renderReviewResults(results) {
|
|
|
+ const reviewResultsList = document.getElementById('reviewResultsList');
|
|
|
+
|
|
|
+ if (!results || results.length === 0) {
|
|
|
+ reviewResultsList.innerHTML = `
|
|
|
+ <div class="empty-state">
|
|
|
+ <div class="empty-state-icon">📭</div>
|
|
|
+ <div class="empty-state-text">暂无审查结果</div>
|
|
|
+ <div class="empty-state-hint">请检查RAG召回是否成功</div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ reviewResultsList.innerHTML = results.map((result, index) => `
|
|
|
+ <div class="review-result-item">
|
|
|
+ <div class="result-item-header">
|
|
|
+ <div class="result-entity-name">
|
|
|
+ <span style="color: #888; font-size: 0.9rem; margin-right: 10px;">#${index + 1}</span>
|
|
|
+ ${result.entity || '未知实体'}
|
|
|
+ </div>
|
|
|
+ <div class="result-rag-score">
|
|
|
+ RAG得分: ${(result.rag_score || 0).toFixed(4)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ ${result.combined_query ? `
|
|
|
+ <div class="query-info">
|
|
|
+ <strong>查询条文:</strong>${escapeHtml(result.combined_query)}
|
|
|
+ </div>
|
|
|
+ ` : ''}
|
|
|
+
|
|
|
+ ${result.reference_source ? `
|
|
|
+ <div class="query-info">
|
|
|
+ <strong>参考来源:</strong>${escapeHtml(result.reference_source)}
|
|
|
+ </div>
|
|
|
+ ` : ''}
|
|
|
+
|
|
|
+ <div class="result-item-content">
|
|
|
+ ${renderReviewBox('非参数审查', result.non_parameter_result)}
|
|
|
+ ${renderReviewBox('参数审查', result.parameter_result)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染单个审查结果盒子
|
|
|
+ */
|
|
|
+function renderReviewBox(title, resultData) {
|
|
|
+ console.log(`[renderReviewBox] ${title}:`, resultData);
|
|
|
+
|
|
|
+ if (!resultData) {
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-warning">未执行</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理错误情况
|
|
|
+ if (resultData.error || resultData.error_message) {
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-error">执行失败</span>
|
|
|
+ <p style="color: #ff5555; margin-top: 10px;">${escapeHtml(resultData.error || resultData.error_message)}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析ReviewResult格式: {success: true, details: {response: "...", ...}}
|
|
|
+ let responseText = null;
|
|
|
+ let parsedResult = null;
|
|
|
+
|
|
|
+ if (resultData.details && resultData.details.response) {
|
|
|
+ responseText = resultData.details.response;
|
|
|
+ console.log(`[renderReviewBox] 从details.response提取:`, responseText.substring(0, 100));
|
|
|
+ } else if (typeof resultData === 'string') {
|
|
|
+ responseText = resultData;
|
|
|
+ console.log(`[renderReviewBox] resultData是字符串:`, responseText.substring(0, 100));
|
|
|
+ } else if (resultData.response) {
|
|
|
+ responseText = resultData.response;
|
|
|
+ console.log(`[renderReviewBox] 从response提取:`, responseText.substring(0, 100));
|
|
|
+ } else {
|
|
|
+ console.log(`[renderReviewBox] 无法提取responseText, resultData类型:`, typeof resultData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有response文本,尝试解析
|
|
|
+ if (responseText) {
|
|
|
+ // 检查是否是"无明显问题"
|
|
|
+ if (responseText.includes('无明显问题')) {
|
|
|
+ console.log(`[renderReviewBox] 检测到"无明显问题"`);
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-success">✅ 无明显问题</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试从Markdown代码块中提取JSON
|
|
|
+ const jsonMatch = responseText.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
|
+ if (jsonMatch) {
|
|
|
+ console.log(`[renderReviewBox] 匹配到Markdown JSON代码块`);
|
|
|
+ try {
|
|
|
+ parsedResult = JSON.parse(jsonMatch[1]);
|
|
|
+ console.log(`[renderReviewBox] JSON解析成功:`, parsedResult);
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('[renderReviewBox] JSON解析失败:', e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 尝试直接解析
|
|
|
+ console.log(`[renderReviewBox] 未找到Markdown代码块,尝试直接解析JSON`);
|
|
|
+ try {
|
|
|
+ parsedResult = JSON.parse(responseText);
|
|
|
+ console.log(`[renderReviewBox] 直接JSON解析成功:`, parsedResult);
|
|
|
+ } catch (e) {
|
|
|
+ // 不是JSON格式,直接显示文本
|
|
|
+ console.log(`[renderReviewBox] 不是JSON格式`);
|
|
|
+ parsedResult = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果解析成功,显示格式化的问题详情
|
|
|
+ if (parsedResult && parsedResult.issue_point) {
|
|
|
+ console.log(`[renderReviewBox] 渲染格式化问题详情`);
|
|
|
+
|
|
|
+ // 提取审查依据信息
|
|
|
+ const ragReferences = resultData.details?.rag_review_references || '';
|
|
|
+ const referenceSource = resultData.details?.rag_reference_source || '';
|
|
|
+
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-warning">⚠️ 发现问题</span>
|
|
|
+ <div class="issue-details">
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>问题标题:</strong>
|
|
|
+ <p>${escapeHtml(parsedResult.issue_point)}</p>
|
|
|
+ </div>
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>问题位置:</strong>
|
|
|
+ <p>${escapeHtml(parsedResult.location)}</p>
|
|
|
+ </div>
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>修改建议:</strong>
|
|
|
+ <p>${escapeHtml(parsedResult.suggestion)}</p>
|
|
|
+ </div>
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>问题原因:</strong>
|
|
|
+ <p>${escapeHtml(parsedResult.reason)}</p>
|
|
|
+ </div>
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>风险等级:</strong>
|
|
|
+ <span class="risk-badge risk-${parsedResult.risk_level === '高风险' ? 'high' : parsedResult.risk_level === '中风险' ? 'medium' : 'low'}">
|
|
|
+ ${escapeHtml(parsedResult.risk_level)}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ ${ragReferences ? `
|
|
|
+ <div class="issue-item">
|
|
|
+ <strong>参考依据:</strong>
|
|
|
+ <button class="view-reference-btn" onclick='showReferenceSidebar(${JSON.stringify(ragReferences).replace(/'/g, "\\'")},"${escapeHtml(referenceSource)}")'>
|
|
|
+ 📖 查看审查依据
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ` : ''}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有原始response但无法解析为结构化数据,显示JSON
|
|
|
+ if (responseText) {
|
|
|
+ console.log(`[renderReviewBox] 渲染原始文本`);
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-warning">⚠️ 发现问题</span>
|
|
|
+ <div class="result-json">
|
|
|
+ <pre>${escapeHtml(responseText)}</pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认情况:显示原始数据
|
|
|
+ console.log(`[renderReviewBox] 渲染默认JSON`);
|
|
|
+ return `
|
|
|
+ <div class="review-result-box">
|
|
|
+ <div class="result-box-title">
|
|
|
+ ${title === '非参数审查' ? '🔰' : '🔢'} ${title}
|
|
|
+ </div>
|
|
|
+ <div class="result-box-content">
|
|
|
+ <span class="status-badge status-warning">⚠️ 发现问题</span>
|
|
|
+ <div class="result-json">
|
|
|
+ <pre>${JSON.stringify(resultData, null, 2)}</pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 工具函数 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * HTML转义
|
|
|
+ */
|
|
|
+function escapeHtml(text) {
|
|
|
+ const div = document.createElement('div');
|
|
|
+ div.textContent = text;
|
|
|
+ return div.innerHTML;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 显示审查依据侧边栏
|
|
|
+ */
|
|
|
+function showReferenceSidebar(references, source) {
|
|
|
+ const sidebar = document.getElementById('referenceSidebar');
|
|
|
+ const overlay = document.getElementById('sidebarOverlay');
|
|
|
+ const content = document.getElementById('sidebarContent');
|
|
|
+
|
|
|
+ // 转换Markdown为HTML
|
|
|
+ let htmlContent = '';
|
|
|
+
|
|
|
+ if (source) {
|
|
|
+ htmlContent += `<div class="reference-source">📄 来源:${escapeHtml(source)}</div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用marked.js转换Markdown
|
|
|
+ if (typeof marked !== 'undefined') {
|
|
|
+ htmlContent += marked.parse(references);
|
|
|
+ } else {
|
|
|
+ // 如果marked未加载,使用纯文本
|
|
|
+ htmlContent += `<pre>${escapeHtml(references)}</pre>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ content.innerHTML = htmlContent;
|
|
|
+
|
|
|
+ // 渲染LaTeX公式
|
|
|
+ if (typeof renderMathInElement !== 'undefined') {
|
|
|
+ renderMathInElement(content, {
|
|
|
+ delimiters: [
|
|
|
+ {left: '$$', right: '$$', display: true},
|
|
|
+ {left: '$', right: '$', display: false},
|
|
|
+ {left: '\\[', right: '\\]', display: true},
|
|
|
+ {left: '\\(', right: '\\)', display: false}
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示侧边栏
|
|
|
+ sidebar.classList.add('active');
|
|
|
+ overlay.classList.add('active');
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 关闭审查依据侧边栏
|
|
|
+ */
|
|
|
+function closeReferenceSidebar() {
|
|
|
+ const sidebar = document.getElementById('referenceSidebar');
|
|
|
+ const overlay = document.getElementById('sidebarOverlay');
|
|
|
+
|
|
|
+ sidebar.classList.remove('active');
|
|
|
+ overlay.classList.remove('active');
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 清空所有数据(统一清空函数)
|
|
|
+ */
|
|
|
+function clearAllData() {
|
|
|
+ document.getElementById('testInput').value = '';
|
|
|
+ document.getElementById('professionalResults').style.display = 'none';
|
|
|
+ window.professionalReviewData = null;
|
|
|
+
|
|
|
+ // 同时清空RAG数据(如果存在clearData函数)
|
|
|
+ if (typeof window.clearData === 'function') {
|
|
|
+ window.clearData();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 清空专业性审查数据(向后兼容)
|
|
|
+ */
|
|
|
+function clearProfessionalData() {
|
|
|
+ clearAllData();
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 初始化 ====================
|
|
|
+
|
|
|
+// 页面加载完成后初始化
|
|
|
+document.addEventListener('DOMContentLoaded', () => {
|
|
|
+ console.log('专业性审查模块已加载');
|
|
|
+});
|