149 lines
5.6 KiB
JavaScript
149 lines
5.6 KiB
JavaScript
// app/static/js/research-tree.js
|
||
|
||
function renderTree(session, outline) {
|
||
const container = document.getElementById('treeContainer');
|
||
if (!container) {
|
||
console.error('Tree container not found!');
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = '';
|
||
|
||
// 根节点 - 始终显示
|
||
const rootNode = document.createElement('div');
|
||
rootNode.className = 'root-node';
|
||
rootNode.innerHTML = `
|
||
<h2>${session.question || '研究问题'}</h2>
|
||
<p>状态:${getStatusText(session.status)} |
|
||
开始时间:${new Date(session.created_at).toLocaleString()}</p>
|
||
${session.error_message ? `<p style="color: #ff6b6b;">错误: ${session.error_message}</p>` : ''}
|
||
`;
|
||
container.appendChild(rootNode);
|
||
|
||
// 如果出错,显示错误信息
|
||
if (session.status === 'error') {
|
||
const errorNode = createTreeNode('研究出现错误', 'error');
|
||
container.appendChild(wrapInTreeNode(errorNode));
|
||
return;
|
||
}
|
||
|
||
// 研究准备节点
|
||
const prepNode = createTreeNode('研究准备', session.refined_questions ? 'completed' : session.status);
|
||
prepNode.onclick = () => showDetail('preparation', session);
|
||
|
||
if (session.refined_questions) {
|
||
const content = document.createElement('div');
|
||
content.className = 'node-content expanded';
|
||
content.innerHTML = `
|
||
<div class="phase-card">
|
||
<h4>🎯 问题细化</h4>
|
||
<ul>
|
||
${session.refined_questions.map(q => `<li>• ${q}</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
`;
|
||
prepNode.querySelector('.node-card').appendChild(content);
|
||
}
|
||
|
||
container.appendChild(wrapInTreeNode(prepNode));
|
||
|
||
// 大纲节点
|
||
if (outline) {
|
||
const outlineNode = createTreeNode('研究大纲', 'completed');
|
||
const outlineContent = document.createElement('div');
|
||
outlineContent.className = 'node-content expanded';
|
||
outlineContent.innerHTML = `
|
||
<div class="phase-card">
|
||
<h4>📋 主要研究问题</h4>
|
||
<ul>
|
||
${outline.research_questions.map(q => `<li>• ${q}</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
`;
|
||
outlineNode.querySelector('.node-card').appendChild(outlineContent);
|
||
|
||
const outlineWrapper = wrapInTreeNode(outlineNode);
|
||
container.appendChild(outlineWrapper);
|
||
|
||
// 子主题节点
|
||
outline.sub_topics.forEach((subtopic, idx) => {
|
||
const subtopicNode = createSubtopicNode(subtopic, idx + 1);
|
||
outlineWrapper.appendChild(wrapInTreeNode(subtopicNode, true));
|
||
});
|
||
} else {
|
||
// 显示大纲创建中或失败
|
||
const outlineStatus = session.status === 'outlining' ? 'processing' :
|
||
session.status === 'error' ? 'error' : 'pending';
|
||
const outlineNode = createTreeNode('研究大纲', outlineStatus);
|
||
container.appendChild(wrapInTreeNode(outlineNode));
|
||
}
|
||
|
||
// 最终报告节点
|
||
const reportNode = createTreeNode('研究报告生成', session.final_report ? 'completed' : 'pending');
|
||
container.appendChild(wrapInTreeNode(reportNode));
|
||
}
|
||
|
||
function createTreeNode(title, status) {
|
||
const node = document.createElement('div');
|
||
const statusInfo = getStatusInfo(status);
|
||
|
||
node.innerHTML = `
|
||
<div class="node-card ${statusInfo.className}">
|
||
<div class="node-header">
|
||
<div class="node-title-wrapper">
|
||
<span class="node-title">${title}</span>
|
||
</div>
|
||
<div class="node-status">
|
||
<span class="status-icon ${statusInfo.className}">${statusInfo.icon}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
return node;
|
||
}
|
||
|
||
function createSubtopicNode(subtopic, index) {
|
||
const node = document.createElement('div');
|
||
const statusInfo = getStatusInfo(subtopic.status);
|
||
|
||
node.innerHTML = `
|
||
<div class="node-card ${statusInfo.className}" onclick="showDetail('subtopic', ${JSON.stringify(subtopic).replace(/"/g, '"')})">
|
||
<div class="node-header">
|
||
<div class="node-title-wrapper">
|
||
<span class="node-title">子主题${index}:${subtopic.topic}</span>
|
||
</div>
|
||
<div class="node-status">
|
||
<span class="priority-badge ${subtopic.priority}">
|
||
${subtopic.priority === 'high' ? '高' : subtopic.priority === 'medium' ? '中' : '低'}优先级
|
||
</span>
|
||
<span class="status-icon ${statusInfo.className}">${statusInfo.icon}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
return node;
|
||
}
|
||
|
||
function wrapInTreeNode(node, isSubtopic = false) {
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'tree-node' + (isSubtopic ? ' subtopic-node' : '');
|
||
wrapper.appendChild(node);
|
||
return wrapper;
|
||
}
|
||
|
||
function getStatusInfo(status) {
|
||
const statusMap = {
|
||
'pending': { icon: '○', className: 'pending' },
|
||
'analyzing': { icon: '●', className: 'processing' },
|
||
'outlining': { icon: '●', className: 'processing' },
|
||
'researching': { icon: '●', className: 'processing' },
|
||
'writing': { icon: '●', className: 'processing' },
|
||
'reviewing': { icon: '●', className: 'processing' },
|
||
'completed': { icon: '✓', className: 'completed' },
|
||
'error': { icon: '✗', className: 'error' },
|
||
'cancelled': { icon: '⊘', className: 'cancelled' }
|
||
};
|
||
return statusMap[status] || statusMap['pending'];
|
||
} |