Phase 5: 完成导出功能
- 新增 export_manager.py 导出管理模块 - 知识图谱导出 SVG/PNG - 实体数据导出 Excel/CSV - 关系数据导出 CSV - 项目报告导出 PDF - 转录文本导出 Markdown - 项目完整数据导出 JSON - 前端添加导出面板和功能 - 更新依赖: pandas, openpyxl, reportlab, cairosvg
This commit is contained in:
216
frontend/app.js
216
frontend/app.js
@@ -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);
|
||||
|
||||
@@ -1406,6 +1406,71 @@
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Export Panel Styles */
|
||||
.export-section {
|
||||
background: #141414;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.export-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
padding: 16px 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
border-color: #00d4ff;
|
||||
background: #1f1f1f;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.export-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.export-label {
|
||||
color: #e0e0e0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.export-desc {
|
||||
color: #666;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.export-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #00d4ff;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.export-success {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
border: 1px solid #00d4ff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
color: #00d4ff;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1565,6 +1630,10 @@
|
||||
<div class="kb-stat-value" id="kbGlossaryCount">0</div>
|
||||
<div class="kb-stat-label">术语</div>
|
||||
</div>
|
||||
<div class="kb-stat" style="cursor:pointer;" onclick="showExportPanel()">
|
||||
<div class="kb-stat-value">📥</div>
|
||||
<div class="kb-stat-label">导出</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kb-content">
|
||||
@@ -1957,6 +2026,81 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Export Panel Modal -->
|
||||
<div class="modal-overlay" id="exportPanelModal" style="display:none;z-index:2000;">
|
||||
<div class="modal" style="max-width:600px;">
|
||||
<div class="modal-header">
|
||||
<h3>📥 导出数据</h3>
|
||||
<button class="close-btn" onclick="hideExportPanel()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="export-section">
|
||||
<h4 style="margin-bottom:12px;color:#00d4ff;">🎨 知识图谱</h4>
|
||||
<div class="export-options">
|
||||
<button class="export-btn" onclick="exportGraph('svg')">
|
||||
<span class="export-icon">🖼️</span>
|
||||
<span class="export-label">导出 SVG</span>
|
||||
<span class="export-desc">矢量图,可编辑</span>
|
||||
</button>
|
||||
<button class="export-btn" onclick="exportGraph('png')">
|
||||
<span class="export-icon">🖼️</span>
|
||||
<span class="export-label">导出 PNG</span>
|
||||
<span class="export-desc">图片格式</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="export-section" style="margin-top:20px;">
|
||||
<h4 style="margin-bottom:12px;color:#00d4ff;">📊 数据导出</h4>
|
||||
<div class="export-options">
|
||||
<button class="export-btn" onclick="exportEntities('excel')">
|
||||
<span class="export-icon">📑</span>
|
||||
<span class="export-label">实体 Excel</span>
|
||||
<span class="export-desc">包含所有属性</span>
|
||||
</button>
|
||||
<button class="export-btn" onclick="exportEntities('csv')">
|
||||
<span class="export-icon">📄</span>
|
||||
<span class="export-label">实体 CSV</span>
|
||||
<span class="export-desc">逗号分隔</span>
|
||||
</button>
|
||||
<button class="export-btn" onclick="exportRelations('csv')">
|
||||
<span class="export-icon">🔗</span>
|
||||
<span class="export-label">关系 CSV</span>
|
||||
<span class="export-desc">关系数据</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="export-section" style="margin-top:20px;">
|
||||
<h4 style="margin-bottom:12px;color:#00d4ff;">📄 文档导出</h4>
|
||||
<div class="export-options">
|
||||
<button class="export-btn" onclick="exportReport('pdf')">
|
||||
<span class="export-icon">📕</span>
|
||||
<span class="export-label">项目报告 PDF</span>
|
||||
<span class="export-desc">完整项目报告</span>
|
||||
</button>
|
||||
<button class="export-btn" onclick="exportProject('json')">
|
||||
<span class="export-icon">🗂️</span>
|
||||
<span class="export-label">项目 JSON</span>
|
||||
<span class="export-desc">完整数据备份</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="export-section" style="margin-top:20px;" id="transcriptExportSection" style="display:none;">
|
||||
<h4 style="margin-bottom:12px;color:#00d4ff;">📝 当前转录文本</h4>
|
||||
<div class="export-options">
|
||||
<button class="export-btn" onclick="exportTranscript('markdown')">
|
||||
<span class="export-icon">📝</span>
|
||||
<span class="export-label">导出 Markdown</span>
|
||||
<span class="export-desc">带实体标注</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Menu -->
|
||||
<div class="context-menu" id="contextMenu">
|
||||
<div class="context-menu-item" onclick="editEntity()">✏️ 编辑实体</div>
|
||||
|
||||
Reference in New Issue
Block a user