Phase 5: 知识推理与问答增强
- 新增 knowledge_reasoner.py 推理引擎 - 支持因果/对比/时序/关联四种推理类型 - 智能项目总结 API (全面/高管/技术/风险) - 实体关联路径发现功能 - 前端推理面板 UI 和交互 - 更新 API 端点和健康检查 Refs: Phase 5 开发任务
This commit is contained in:
266
frontend/app.js
266
frontend/app.js
@@ -924,6 +924,7 @@ window.switchView = function(viewName) {
|
||||
document.getElementById('workbenchView').style.display = 'none';
|
||||
document.getElementById('knowledgeBaseView').classList.remove('show');
|
||||
document.getElementById('timelineView').classList.remove('show');
|
||||
document.getElementById('reasoningView').classList.remove('active');
|
||||
|
||||
// 显示选中的视图
|
||||
if (viewName === 'workbench') {
|
||||
@@ -937,6 +938,9 @@ window.switchView = function(viewName) {
|
||||
document.getElementById('timelineView').classList.add('show');
|
||||
document.querySelector('.sidebar-btn:nth-child(3)').classList.add('active');
|
||||
loadTimeline();
|
||||
} else if (viewName === 'reasoning') {
|
||||
document.getElementById('reasoningView').classList.add('active');
|
||||
document.querySelector('.sidebar-btn:nth-child(4)').classList.add('active');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1109,3 +1113,265 @@ window.deleteGlossaryTerm = async function(termId) {
|
||||
console.error('Delete glossary term failed:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== Phase 5: Knowledge Reasoning ====================
|
||||
|
||||
window.submitReasoningQuery = async function() {
|
||||
const input = document.getElementById('reasoningInput');
|
||||
const depth = document.getElementById('reasoningDepth').value;
|
||||
const query = input.value.trim();
|
||||
|
||||
if (!query) return;
|
||||
|
||||
const resultsDiv = document.getElementById('reasoningResults');
|
||||
|
||||
// 显示加载状态
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<div class="typing-indicator">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<p style="color:#666;text-align:center;">正在进行知识推理...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/reasoning/query`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, reasoning_depth: depth })
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Reasoning failed');
|
||||
|
||||
const data = await res.json();
|
||||
renderReasoningResult(data);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Reasoning query failed:', err);
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<p style="color:#ff6b6b;">推理失败,请稍后重试</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
function renderReasoningResult(data) {
|
||||
const resultsDiv = document.getElementById('reasoningResults');
|
||||
|
||||
const typeLabels = {
|
||||
'causal': '🔍 因果推理',
|
||||
'comparative': '⚖️ 对比推理',
|
||||
'temporal': '⏱️ 时序推理',
|
||||
'associative': '🔗 关联推理',
|
||||
'summary': '📝 总结推理'
|
||||
};
|
||||
|
||||
const typeLabel = typeLabels[data.reasoning_type] || '🤔 智能分析';
|
||||
const confidencePercent = Math.round(data.confidence * 100);
|
||||
|
||||
let evidenceHtml = '';
|
||||
if (data.evidence && data.evidence.length > 0) {
|
||||
evidenceHtml = `
|
||||
<div class="reasoning-evidence">
|
||||
<h4>📋 支撑证据</h4>
|
||||
${data.evidence.map(e => `<div class="evidence-item">${e.text || e}</div>`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let gapsHtml = '';
|
||||
if (data.knowledge_gaps && data.knowledge_gaps.length > 0) {
|
||||
gapsHtml = `
|
||||
<div class="reasoning-gaps">
|
||||
<h4>⚠️ 知识缺口</h4>
|
||||
<ul>
|
||||
${data.knowledge_gaps.map(g => `<li>${g}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<div class="reasoning-result-header">
|
||||
<div class="reasoning-result-type">
|
||||
<span>${typeLabel}</span>
|
||||
</div>
|
||||
<div class="reasoning-confidence">
|
||||
置信度: ${confidencePercent}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="reasoning-answer">
|
||||
${data.answer.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
${evidenceHtml}
|
||||
${gapsHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
window.clearReasoningResult = function() {
|
||||
document.getElementById('reasoningResults').innerHTML = '';
|
||||
document.getElementById('reasoningInput').value = '';
|
||||
document.getElementById('inferencePathsSection').style.display = 'none';
|
||||
};
|
||||
|
||||
window.generateSummary = async function(summaryType) {
|
||||
const resultsDiv = document.getElementById('reasoningResults');
|
||||
|
||||
// 显示加载状态
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<div class="typing-indicator">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<p style="color:#666;text-align:center;">正在生成项目总结...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/reasoning/summary`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ summary_type: summaryType })
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error('Summary failed');
|
||||
|
||||
const data = await res.json();
|
||||
renderSummaryResult(data);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Summary generation failed:', err);
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<p style="color:#ff6b6b;">总结生成失败,请稍后重试</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
function renderSummaryResult(data) {
|
||||
const resultsDiv = document.getElementById('reasoningResults');
|
||||
|
||||
const typeLabels = {
|
||||
'comprehensive': '📋 全面总结',
|
||||
'executive': '💼 高管摘要',
|
||||
'technical': '⚙️ 技术总结',
|
||||
'risk': '⚠️ 风险分析'
|
||||
};
|
||||
|
||||
const typeLabel = typeLabels[data.summary_type] || '📝 项目总结';
|
||||
|
||||
let keyPointsHtml = '';
|
||||
if (data.key_points && data.key_points.length > 0) {
|
||||
keyPointsHtml = `
|
||||
<div style="margin: 16px 0;">
|
||||
<h4 style="color:#888;font-size:0.9rem;margin-bottom:12px;">📌 关键要点</h4>
|
||||
<ul style="margin:0;padding-left:20px;color:#aaa;line-height:1.8;">
|
||||
${data.key_points.map(p => `<li>${p}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let risksHtml = '';
|
||||
if (data.risks && data.risks.length > 0) {
|
||||
risksHtml = `
|
||||
<div class="reasoning-gaps">
|
||||
<h4>⚠️ 风险与问题</h4>
|
||||
<ul>
|
||||
${data.risks.map(r => `<li>${r}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let recommendationsHtml = '';
|
||||
if (data.recommendations && data.recommendations.length > 0) {
|
||||
recommendationsHtml = `
|
||||
<div class="reasoning-evidence">
|
||||
<h4>💡 建议</h4>
|
||||
${data.recommendations.map(r => `<div class="evidence-item">${r}</div>`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="reasoning-result">
|
||||
<div class="reasoning-result-header">
|
||||
<div class="reasoning-result-type">
|
||||
<span>${typeLabel}</span>
|
||||
</div>
|
||||
<div class="reasoning-confidence">
|
||||
置信度: ${Math.round(data.confidence * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="reasoning-answer">
|
||||
${data.overview ? data.overview.replace(/\n/g, '<br>') : ''}
|
||||
</div>
|
||||
${keyPointsHtml}
|
||||
${risksHtml}
|
||||
${recommendationsHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
window.findInferencePath = async function(startEntity, endEntity) {
|
||||
const pathsSection = document.getElementById('inferencePathsSection');
|
||||
const pathsList = document.getElementById('inferencePathsList');
|
||||
|
||||
pathsSection.style.display = 'block';
|
||||
pathsList.innerHTML = '<p style="color:#666;">正在搜索关联路径...</p>';
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/projects/${currentProject.id}/reasoning/inference-path?start_entity=${encodeURIComponent(startEntity)}&end_entity=${encodeURIComponent(endEntity)}`
|
||||
);
|
||||
|
||||
if (!res.ok) throw new Error('Path finding failed');
|
||||
|
||||
const data = await res.json();
|
||||
renderInferencePaths(data);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Path finding failed:', err);
|
||||
pathsList.innerHTML = '<p style="color:#ff6b6b;">路径搜索失败</p>';
|
||||
}
|
||||
};
|
||||
|
||||
function renderInferencePaths(data) {
|
||||
const pathsList = document.getElementById('inferencePathsList');
|
||||
|
||||
if (!data.paths || data.paths.length === 0) {
|
||||
pathsList.innerHTML = '<p style="color:#666;">未找到关联路径</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
pathsList.innerHTML = data.paths.map((path, idx) => {
|
||||
const strengthPercent = Math.round(path.strength * 100);
|
||||
const pathHtml = path.path.map((node, i) => {
|
||||
if (i === 0) {
|
||||
return `<span class="path-node">${node.entity}</span>`;
|
||||
}
|
||||
return `
|
||||
<span class="path-arrow">→ ${node.relation} →</span>
|
||||
<span class="path-node">${node.entity}</span>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="inference-path">
|
||||
<div class="inference-path-header">
|
||||
<span style="color:#888;font-size:0.85rem;">路径 ${idx + 1}</span>
|
||||
<span class="path-strength">关联强度: ${strengthPercent}%</span>
|
||||
</div>
|
||||
<div class="path-visual">
|
||||
${pathHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user