Phase 5: 完成导出功能

- 新增 export_manager.py 导出管理模块
- 知识图谱导出 SVG/PNG
- 实体数据导出 Excel/CSV
- 关系数据导出 CSV
- 项目报告导出 PDF
- 转录文本导出 Markdown
- 项目完整数据导出 JSON
- 前端添加导出面板和功能
- 更新依赖: pandas, openpyxl, reportlab, cairosvg
This commit is contained in:
OpenClaw Bot
2026-02-20 06:06:23 +08:00
parent 2470064f65
commit 6318cd0af9
6 changed files with 1365 additions and 1 deletions

View File

@@ -1809,3 +1809,219 @@ window.searchByAttributes = async function() {
alert('搜索失败');
}
};
// ==================== Export Functions ====================
// Show export panel
window.showExportPanel = function() {
const modal = document.getElementById('exportPanelModal');
if (modal) {
modal.style.display = 'flex';
// Show transcript export section if a transcript is selected
const transcriptSection = document.getElementById('transcriptExportSection');
if (transcriptSection && currentData && currentData.transcript_id !== 'project_view') {
transcriptSection.style.display = 'block';
} else if (transcriptSection) {
transcriptSection.style.display = 'none';
}
}
};
// Hide export panel
window.hideExportPanel = function() {
const modal = document.getElementById('exportPanelModal');
if (modal) {
modal.style.display = 'none';
}
};
// Helper function to download file
function downloadFile(url, filename) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Export knowledge graph as SVG
window.exportGraph = async function(format) {
if (!currentProject) return;
try {
const endpoint = format === 'svg' ? 'graph-svg' : 'graph-png';
const mimeType = format === 'svg' ? 'image/svg+xml' : 'image/png';
const ext = format === 'svg' ? 'svg' : 'png';
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/export/${endpoint}`);
if (!res.ok) throw new Error(`Export ${format} failed`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-graph-${currentProject.id}.${ext}`);
URL.revokeObjectURL(url);
showNotification(`图谱已导出为 ${format.toUpperCase()}`, 'success');
} catch (err) {
console.error(`Export ${format} failed:`, err);
alert(`导出失败: ${err.message}`);
}
};
// Export entities
window.exportEntities = async function(format) {
if (!currentProject) return;
try {
const endpoint = format === 'excel' ? 'entities-excel' : 'entities-csv';
const mimeType = format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv';
const ext = format === 'excel' ? 'xlsx' : 'csv';
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/export/${endpoint}`);
if (!res.ok) throw new Error(`Export ${format} failed`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-entities-${currentProject.id}.${ext}`);
URL.revokeObjectURL(url);
showNotification(`实体数据已导出为 ${format.toUpperCase()}`, 'success');
} catch (err) {
console.error(`Export ${format} failed:`, err);
alert(`导出失败: ${err.message}`);
}
};
// Export relations
window.exportRelations = async function(format) {
if (!currentProject) return;
try {
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/export/relations-csv`);
if (!res.ok) throw new Error('Export relations failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-relations-${currentProject.id}.csv`);
URL.revokeObjectURL(url);
showNotification('关系数据已导出为 CSV', 'success');
} catch (err) {
console.error('Export relations failed:', err);
alert(`导出失败: ${err.message}`);
}
};
// Export project report as PDF
window.exportReport = async function(format) {
if (!currentProject) return;
try {
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/export/report-pdf`);
if (!res.ok) throw new Error('Export PDF failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-report-${currentProject.id}.pdf`);
URL.revokeObjectURL(url);
showNotification('项目报告已导出为 PDF', 'success');
} catch (err) {
console.error('Export PDF failed:', err);
alert(`导出失败: ${err.message}`);
}
};
// Export project as JSON
window.exportProject = async function(format) {
if (!currentProject) return;
try {
const res = await fetch(`${API_BASE}/projects/${currentProject.id}/export/project-json`);
if (!res.ok) throw new Error('Export JSON failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-project-${currentProject.id}.json`);
URL.revokeObjectURL(url);
showNotification('项目数据已导出为 JSON', 'success');
} catch (err) {
console.error('Export JSON failed:', err);
alert(`导出失败: ${err.message}`);
}
};
// Export transcript as Markdown
window.exportTranscript = async function(format) {
if (!currentProject || !currentData || currentData.transcript_id === 'project_view') {
alert('请先选择一个转录文件');
return;
}
try {
const res = await fetch(`${API_BASE}/transcripts/${currentData.transcript_id}/export/markdown`);
if (!res.ok) throw new Error('Export Markdown failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
downloadFile(url, `insightflow-transcript-${currentData.transcript_id}.md`);
URL.revokeObjectURL(url);
showNotification('转录文本已导出为 Markdown', 'success');
} catch (err) {
console.error('Export Markdown failed:', err);
alert(`导出失败: ${err.message}`);
}
};
// Show notification
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? 'rgba(0, 212, 255, 0.9)' : '#333'};
color: ${type === 'success' ? '#000' : '#fff'};
padding: 12px 20px;
border-radius: 8px;
z-index: 10000;
font-size: 0.9rem;
animation: slideIn 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// Add animation styles
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);