This commit is contained in:
2025-01-17 08:19:30 +00:00
parent a5357af694
commit 19699e3109
3 changed files with 788 additions and 463 deletions
+54
View File
@@ -0,0 +1,54 @@
# Papers 文献分析系统
该项目是一个用于管理和分析学术文献的系统,使用FastAPI作为后端框架,并提供了一个基于HTML和JavaScript的前端界面。
## 更新日志
### 2025-01-17 更新
1. 优化report设计,接入了memarid.js,支持流程图展示(已完成)
### 2025-01-16 更新
Initial commit(初始化,未完成)
## 功能
- **文献上传**:支持批量上传PDF和Word文档。
- **文献分析**:上传的文献会被自动分析,提取基本信息和内容分析。
- **文献管理**:可以查看、删除和下载文献分析报告。
- **问答功能**:用户可以就文献内容提出问题,系统会基于分析结果提供回答。
- **流程图展示**:使用Mermaid.js展示文献的研究方法流程图。
## 使用说明
### 文件结构
paper/
├── #后端
├── paper.py # 后端程序
├── paper.html # web页面
└── README.md # 项目说明
### 后端 paper.py https://dev.obscura.work/paper
1. 服务器:222.186.10.253
2. PORT = 9005
3. 数据库:
mongodb: 222.186.10.253:27017/paper
Redis222.186.10.253:6379; db190-192
### web页面 https://beta.obscura.work/paper/paper.html
## 使用说明
1. **上传文献**:点击“Upload”按钮,选择要上传的文献文件。
2. **查看文献列表**:上传后,文献会显示在列表中,点击“View Details”查看详细分析。
3. **删除文献**:点击“Delete”按钮删除文献。
4. **问答功能**:点击聊天图标,输入问题,系统会基于文献内容提供回答。
5. **下载报告**:在文献详情页面,可以下载分析报告。
## 注意事项
- 确保MongoDB和Redis服务已启动并正确配置。
- 上传的文献文件类型仅支持PDF和Word文档。
- 文献分析可能需要一些时间,请耐心等待。
+281 -176
View File
@@ -3,9 +3,22 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paper Analysis</title>
<title>Papers</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
window.mermaid = mermaid;
mermaid.initialize({
startOnLoad: true,
theme: 'default',
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: 'basis'
}
});
</script>
<style>
/* 基础样式优化 */
body {
@@ -351,17 +364,14 @@
</head>
<body>
<nav class="navbar">
<div class="navbar-brand">Paper</div>
<div class="navbar-menu">
<div class="navbar-menu-item active" onclick="window.location.href='Paper.html'">Paper</div>
</div>
<div class="navbar-brand">Papers</div>
</nav>
<div class="container-fluid">
<div class="row">
<!-- 左侧侧边栏 -->
<div class="col-auto">
<div class="sidebar">
<!-- 项目文献页面的侧边栏 -->
<!-- 文献页面的侧边栏 -->
<div class="sidebar-menu hidden" id="DetailSidebar">
<div class="sidebar-item" onclick="showPaperList()">
<i class="bi bi-list-ul"></i>
@@ -374,39 +384,19 @@
<!-- 右侧主要内容区 -->
<div class="col">
<div class="content-wrapper">
<!-- 项目文献详情页面 -->
<!-- 文献详情页面 -->
<div id="DetailPage" class="content-body hidden">
<div class="content-header d-flex justify-content-between align-items-center">
<h3 class="content-title" id="referenceAnalysisTitle">Paper Analysis</h3>
<button class="btn btn-info" onclick="uploadPaper()">
<h3 class="content-title" id="paperTitle">Paper List</h3>
<button class="btn btn-info" onclick="uploadPaper()" style="padding: 10px 20px;">
<i class="bi bi-upload me-2"></i>
Upload
</button>
</div>
<div id="referenceAnalysis" class="mt-4">
<div id="paper" class="mt-4">
<!-- 文献分析内容将在这里动态加载 -->
</div>
</div>
<!-- 文献分析报告页面 -->
<div id="paperReportPage" class="content-body hidden">
<div class="content-header d-flex justify-content-between align-items-center">
<h3 class="content-title" id="paperReportTitle">Paper Summary Report</h3>
<div class="d-flex gap-2">
<button class="btn btn-infoy" onclick="regenerateReport()">
<i class="bi bi-arrow-clockwise me-2"></i>
Regenerate
</button>
<button class="btn btn-info" onclick="downloadReport()">
<i class="bi bi-download me-2"></i>
Download
</button>
</div>
</div>
<div id="paperReportContent" class="mt-4">
<!-- 报告内容将在这里动态加载 -->
</div>
</div>
</div>
</div>
</div>
@@ -416,16 +406,13 @@
<script>
// 全局变量
const API_BASE_URL = 'https://dev.obscura.work/paper'; // 修改为正确的API地址
let currentReportData = null;
let currentReportData = null; // 添加存储当前报告数据的变量
// 页面加载时加载项目列表
document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('DetailPage').classList.remove('hidden');
await loadReferences();
});
// 添加一个全局变量来存储论文报告数据
const paperReports = new Map();
// Add show paper analysis page function
async function showReferenceAnalysis() {
async function showpaper() {
try {
// Hide other pages
document.querySelectorAll('.page-content').forEach(page => {
@@ -433,14 +420,14 @@
});
// Show paper analysis page
document.getElementById('referenceAnalysisPage').classList.add('active');
document.getElementById('paperPage').classList.add('active');
// Set title
document.getElementById('referenceAnalysisTitle').textContent =
`Paper Analysis - ${document.getElementById('projectDetailTitle').textContent}`;
document.getElementById('paperTitle').textContent =
`Paper Analysis`;
// Load paper list
await loadReferences(currentProjectId);
await loadPapers();
} catch (error) {
console.error('Failed to show Paper analysis page:', error);
alert('Failed to show Paper analysis page, please try again');
@@ -448,16 +435,16 @@
}
// paper card rendering function
function renderReferenceCard(reference) {
const uploadTime = reference.upload_time;
const cardId = `reference-${reference._id}`;
function renderPaperCard(paper) {
const uploadTime = paper.upload_time;
const cardId = `paper-${paper._id}`;
return `
<div class="card mb-3" id="${cardId}">
<div class="card-body d-flex position-relative">
<div class="flex-grow-1" style="cursor: pointer;" onclick="viewReferenceDetailFromCard(this.closest('.card'))">
<h5 class="card-title">${reference.reference_title}</h5>
<div class="card-text analysis-content-${reference._id}">
<div class="flex-grow-1" style="cursor: pointer;" onclick="DetailFromCard(this.closest('.card'))">
<h5 class="card-title">${paper.paper_title}</h5>
<div class="card-text analysis-content-${paper._id}">
<div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm text-primary me-2" role="status">
<span class="visually-hidden">Loading...</span>
@@ -467,11 +454,11 @@
</div>
<div class="btn-group">
<button class="btn btn-warning btn-sm"
onclick="event.stopPropagation(); viewReferenceDetailFromCard(this.closest('.card'))">
onclick="event.stopPropagation(); DetailFromCard(this.closest('.card'))">
View Details
</button>
<button class="btn btn-danger btn-sm"
onclick="event.stopPropagation(); deleteReference('${reference._id}')">
onclick="event.stopPropagation(); deletepaper('${paper._id}')">
Delete
</button>
</div>
@@ -479,49 +466,49 @@
<div class="position-absolute" style="right: 1rem; top: 50%; transform: translateY(-50%);">
<button class="btn rounded-circle d-flex align-items-center justify-content-center"
style="width: 48px; height: 48px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); background: var(--primary-color); color: white;"
onclick="event.stopPropagation(); openChatDialog('${reference._id}', '${reference.reference_title}')">
onclick="event.stopPropagation(); openChatDialog('${paper._id}')">
<i class="bi bi-chat-dots" style="font-size: 1.2rem;"></i>
</button>
</div>
</div>
<div class="card-footer text-muted">
Upload Time: ${uploadTime}
<span class="ms-2 analysis-status-${reference._id}">Analysis in progress...</span>
<span class="ms-2 analysis-status-${paper._id}">Analysis in progress...</span>
</div>
</div>
`;
}
// Load paper list function
async function loadReferences() {
const referencesList = document.getElementById('referenceAnalysis');
async function loadPapers() {
const papersList = document.getElementById('paper');
try {
// Get paper list
const response = await fetch(`${API_BASE_URL}`);
const response = await fetch(`${API_BASE_URL}/papers`);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || 'Failed to fetch references');
throw new Error(errorData.detail || 'Failed to fetch papers');
}
const references = await response.json();
const papers = await response.json();
if (!Array.isArray(references)) {
if (!Array.isArray(papers)) {
throw new Error('Invalid data format: expected an array');
}
if (references.length === 0) {
referencesList.innerHTML = '<div class="alert alert-info">No paper data, please upload paper</div>';
if (papers.length === 0) {
papersList.innerHTML = '<div class="alert alert-info">No paper, please upload paper</div>';
return;
}
// Render paper cards
const referenceCards = references.map(ref => renderReferenceCard(ref)).join('');
referencesList.innerHTML = referenceCards;
const paperCards = papers.map(ref => renderPaperCard(ref)).join('');
papersList.innerHTML = paperCards;
// Get analysis status and results for each paper
references.forEach(async (ref) => {
papers.forEach(async (ref) => {
try {
const checkAnalysisStatus = async () => {
const analysisResponse = await fetch(`${API_BASE_URL}/${ref._id}/report`);
@@ -531,6 +518,9 @@
}
const analysisResult = await analysisResponse.json();
// 存储报告数据
paperReports.set(ref._id, analysisResult);
const contentElement = document.querySelector(`.analysis-content-${ref._id}`);
const statusElement = document.querySelector(`.analysis-status-${ref._id}`);
@@ -562,7 +552,7 @@
journal = basicInfo.journal_publisher || 'N/A';
// 更新卡片标题为分析结果中的标题
const titleElement = document.querySelector(`#reference-${ref._id} .card-title`);
const titleElement = document.querySelector(`#paper-${ref._id} .card-title`);
if (titleElement && basicInfo.title) {
titleElement.textContent = basicInfo.title;
}
@@ -592,7 +582,7 @@
// 开始首次检查
await checkAnalysisStatus();
} catch (error) {
console.error(`Failed to fetch analysis for reference ${ref._id}:`, error);
console.error(`Failed to fetch analysis for paper ${ref._id}:`, error);
const contentElement = document.querySelector(`.analysis-content-${ref._id}`);
const statusElement = document.querySelector(`.analysis-status-${ref._id}`);
@@ -608,10 +598,19 @@
} catch (error) {
console.error('Failed to load Paper list:', error);
papersList.innerHTML = `
<div class="alert alert-danger">
Failed to load Paper list: ${error.message}
<br>
<button class="btn btn-primary mt-2" onclick="loadpapers()">
Retry
</button>
</div>`;
}
}
async function uploadPaper() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.pdf,.doc,.docx';
@@ -660,7 +659,7 @@
progressCardBody.appendChild(progressInner);
progressCard.appendChild(progressCardBody);
const titleElement = document.getElementById('referenceAnalysisTitle');
const titleElement = document.getElementById('paperTitle');
titleElement.parentNode.parentNode.insertBefore(progressCard, titleElement.parentNode);
try {
@@ -669,6 +668,7 @@
formData.append('files', file);
});
// 使用 XMLHttpRequest 来获取上传进度
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(e) {
@@ -690,7 +690,7 @@
xhr.onerror = () => reject(new Error('Upload failed'));
});
xhr.open('POST', `${API_BASE_URL}/analyze`);
xhr.open('POST', `${API_BASE_URL}/upload`);
xhr.send(formData);
await uploadPromise;
@@ -705,7 +705,7 @@
}, 500);
// 刷新文献列表
await loadReferences();
await loadPapers();
} catch (error) {
console.error('Upload failed:', error);
@@ -718,9 +718,9 @@
}
// View paper details function
async function viewReferenceDetail(referenceId) {
async function viewDetail(paperId) {
try {
const response = await fetch(`${API_BASE_URL}/${referenceId}/report`);
const response = await fetch(`${API_BASE_URL}/${paperId}/report`);
if (!response.ok) {
throw new Error('Failed to fetch analysis report');
@@ -744,14 +744,36 @@
.replace(/},/g, '')
.replace(/{ },/g, '')
.replace(/^\s*,/gm, '')
.replace(/Paper Analysis Report:/, '<div style="text-align: center"><strong>Paper Analysis</strong></div>')
.replace(/^(\s*\d+\.[^:\n]+:)/gm, '<strong>$1</strong>')
.replace(/Paper Analysis Report:/, '<div style="text-align: center; margin-bottom: 20px;"><strong style="font-size: 1.5rem;">Paper Analysis</strong></div>')
.replace(/^(\s*\d+\.[^:\n]+:)/gm, '<strong style="color: #6366F1;font-size: 1.2rem;">$1</strong>')
.replace(/(author|publication_date|title|journal_publisher|document_type):/g, '<span style="font-weight: bold; font-size: 1rem;">$1:</span>')
.replace(/(abstract|research_purpose|methodology|main_arguments|conclusions|innovations|flowchart):/g, '<span style="font-weight: bold; font-size: 1rem;">$1:</span>')
.replace(/(academic_contribution|practical_significance|limitations|implications):/g, '<span style="font-weight: bold; font-size: 1rem;">$1:</span>')
.split('\n')
.map(line => line.trimEnd())
.join('\n');
.map(line => `<p style="margin: 0 0 10px;">${line.trimEnd()}</p>`)
.join('');
// 检查是否有流程图数据
let flowchartData = analysisResult.flowchart || 'No flowchart data available';
modalContent = `
<pre class="report-content" style="white-space: pre-wrap; word-wrap: break-word;">${formattedReport}</pre>
<div class="d-flex">
<div class="flex-grow-1 pe-3" style="width: 70%;">
<h5 class="mb-2" style="font-size: 1.2rem; font-weight: 600;">Report</h5>
<div style="background-color: white; padding: 10px 35px 10px 35px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); height: calc(80vh - 30px);">
<pre class="report-content custom-scrollbar" style="white-space: pre-wrap; word-wrap: break-word; height: 100%; overflow-y: auto; scrollbar-width: 4px; scrollbar-color: #d3d3d3 transparent; scrollbar-gutter: stable; margin: 0;padding: 10px 20px 10px 20px;">
${formattedReport}
</pre>
</div>
</div>
<div class="flex-grow-1 ps-3" style="width: 30%;">
<h5 class="mb-2" style="font-size: 1.2rem; font-weight: 600;">FlowChart</h5>
<div class="mermaid-wrapper custom-scrollbar" style="background-color: white; padding: 10px 35px 10px 35px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); height: calc(80vh - 30px); overflow: auto; scrollbar-width: 4px; scrollbar-color: #d3d3d3 transparent; scrollbar-gutter: stable;padding: 10px 20px 10px;">
<div class="mermaid">
${flowchartData}
</div>
</div>
</div>
</div>
`;
} else if (analysisResult.status === 'failed') {
modalContent = `
@@ -764,19 +786,19 @@
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'referenceDetailModal';
modal.id = 'paperDetailModal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'referenceDetailModalTitle');
modal.setAttribute('aria-labelledby', 'paperDetailModalTitle');
modal.innerHTML = `
<div class="modal-dialog modal-lg" style="max-width: 80%; margin: 1.75rem auto;">
<div class="modal-content" style="min-height: 80vh;">
<div class="modal-dialog modal-lg" style="max-width: 70%; margin: 1.75rem auto;">
<div class="modal-content" style="min-height: 90vh;">
<div class="modal-header">
<h5 class="modal-title" id="referenceDetailModalTitle">Paper Analysis</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title" id="paperDetailModalTitle">Paper Analysis</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" style="outline: none; box-shadow: none;"></button>
</div>
<div class="modal-body" style="max-height: calc(80vh - 60px); overflow-y: auto;">
<div class="modal-body" style="padding: 20px;">
${modalContent}
</div>
</div>
@@ -785,12 +807,43 @@
document.body.appendChild(modal);
const modalInstance = new bootstrap.Modal(modal, {
keyboard: true,
focus: true
backdrop: 'static', // 禁用点击空白处关闭
keyboard: false // 禁用 ESC 键关闭
});
modalInstance.show();
// 初始化 Mermaid 图表
if (analysisResult.status === 'completed' && analysisResult.flowchart) {
// 在模态框显示后重新初始化 Mermaid
modal.addEventListener('shown.bs.modal', async () => {
try {
// 清除之前的图表
const mermaidDiv = modal.querySelector('.mermaid');
if (mermaidDiv) {
mermaidDiv.innerHTML = analysisResult.flowchart;
// 重新初始化 mermaid
await window.mermaid.init();
// 如果初始化失败,尝试重新渲染
if (!mermaidDiv.querySelector('svg')) {
window.mermaid.contentLoaded();
}
}
} catch (error) {
console.error('Failed to initialize mermaid:', error);
const mermaidDiv = modal.querySelector('.mermaid');
if (mermaidDiv) {
mermaidDiv.innerHTML = `
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
Failed to render flowchart. Please try closing and reopening this dialog.
</div>
`;
}
}
});
}
modal.addEventListener('hide.bs.modal', () => {
const focusedElement = document.activeElement;
if (modal.contains(focusedElement)) {
@@ -809,32 +862,31 @@
}
// View paper details from card
function viewReferenceDetailFromCard(card) {
const referenceId = card.id.replace('reference-', '');
viewReferenceDetail(referenceId);
function DetailFromCard(card) {
const paperId = card.id.replace('paper-', '');
viewDetail(paperId);
}
// Delete paper function
async function deleteReference(referenceId) {
async function deletePaper(paperId) {
if (!confirm('Are you sure you want to delete this Paper? This action cannot be undone.')) {
return;
}
try {
const response = await fetch(`${API_BASE_URL}/${referenceId}`, {
const response = await fetch(`${API_BASE_URL}/delete/${paperId}`, {
method: 'DELETE'
});
if (response.ok) {
const card = document.getElementById(`reference-${referenceId}`);
const card = document.getElementById(`paper-${paperId}`);
if (card) {
card.remove();
}
const referencesList = document.getElementById('referenceAnalysis');
if (!referencesList.children.length) {
referencesList.innerHTML = '<div class="alert alert-info">No Paper data, please upload</div>';
const papersList = document.getElementById('paper');
if (!papersList.children.length) {
papersList.innerHTML = '<div class="alert alert-info">No Paper data, please upload</div>';
}
alert('Deleted successfully');
@@ -848,90 +900,103 @@
}
}
// Add chat dialog function
async function openChatDialog(referenceId, title) {
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'chatModal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'chatModalTitle');
modal.innerHTML = `
<div class="modal-dialog modal-lg modal-dialog-centered" style="max-width: 800px;">
<div class="modal-content" style="min-height: 60vh;">
<div class="modal-header">
<h5 class="modal-title" id="chatModalTitle">${title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex flex-column" style="height: calc(60vh - 76px);">
<div id="chatHistory" class="flex-grow-1 custom-scrollbar" style="overflow-y: auto; margin-bottom: 15px; padding: 10px;">
<!-- Chat history will be loaded here -->
async function openChatDialog(paperId) {
try {
// 使用已获取的报告数据
const report = paperReports.get(paperId);
let paperTitle = 'Chat';
// 从报告中获取标题
if (report && report.status === 'completed' && report['1. Basic Information'] && report['1. Basic Information'].title) {
paperTitle = report['1. Basic Information'].title;
}
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'chatModal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'chatModalTitle');
modal.innerHTML = `
<div class="modal-dialog modal-lg modal-dialog-centered" style="max-width: 800px;">
<div class="modal-content" style="min-height: 60vh;">
<div class="modal-header">
<h5 class="modal-title" id="chatModalTitle">${paperTitle}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="input-group">
<input type="text" id="questionInput" class="form-control"
placeholder="Type your question here..."
aria-label="Question input">
<button class="btn"
style="background: var(--primary-color); color: white;"
onclick="sendQuestion('${referenceId}')"
aria-label="Send question">
Send
</button>
<div class="modal-body d-flex flex-column" style="height: calc(60vh - 76px);">
<div id="chatHistory" class="flex-grow-1 custom-scrollbar" style="overflow-y: auto; margin-bottom: 15px; padding: 10px;">
<!-- Chat history will be loaded here -->
</div>
<div class="input-group">
<input type="text" id="questionInput" class="form-control"
placeholder="Type your question here..."
aria-label="Question input">
<button class="btn"
style="background: var(--primary-color); color: white;"
onclick="sendQuestion('${paperId}')"
aria-label="Send question">
Send
</button>
</div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// 使用更多的 Modal 选项来控制行为
const modalInstance = new bootstrap.Modal(modal, {
backdrop: 'static', // 静态背景
keyboard: false, // 禁用键盘关闭
focus: true // 自动聚焦
});
// 在显示模态框之前添加事件监听器
modal.addEventListener('show.bs.modal', () => {
// 确保模态框可以正确接收焦点
modal.removeAttribute('aria-hidden');
});
// 在隐藏模态框之前清除焦点
modal.addEventListener('hide.bs.modal', () => {
const focusedElement = document.activeElement;
if (modal.contains(focusedElement)) {
focusedElement.blur();
}
});
// 在模态框完全隐藏后移除它
modal.addEventListener('hidden.bs.modal', () => {
modal.remove();
});
modalInstance.show();
// 加载聊天历史
await loadChatHistory(referenceId);
// 添加回车键事件监听器
const input = document.getElementById('questionInput');
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendQuestion(referenceId);
}
});
`;
document.body.appendChild(modal);
// 使用更多的 Modal 选项来控制行为
const modalInstance = new bootstrap.Modal(modal, {
backdrop: 'static', // 静态背景
keyboard: false, // 禁用键盘关闭
focus: true // 自动聚焦
});
// 在显示模态框之前添加事件监听器
modal.addEventListener('show.bs.modal', () => {
// 确保模态框可以正确接收焦点
modal.removeAttribute('aria-hidden');
});
// 在隐藏模态框之前清除焦点
modal.addEventListener('hide.bs.modal', () => {
const focusedElement = document.activeElement;
if (modal.contains(focusedElement)) {
focusedElement.blur();
}
});
// 在模态框完全隐藏后移除它
modal.addEventListener('hidden.bs.modal', () => {
modal.remove();
});
modalInstance.show();
// 加载聊天历史
await loadChatHistory(paperId);
// 添加回车键事件监听器
const input = document.getElementById('questionInput');
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendQuestion(paperId);
}
});
} catch (error) {
console.error('Failed to open chat dialog:', error);
alert('Failed to open chat dialog');
}
}
// Load chat history function
async function loadChatHistory(referenceId) {
async function loadChatHistory(paperId) {
const chatHistory = document.getElementById('chatHistory');
try {
const response = await fetch(`${API_BASE_URL}/${referenceId}/qa/history`);
const response = await fetch(`${API_BASE_URL}/${paperId}/qa/history`);
if (!response.ok) {
throw new Error('Failed to load chat history');
@@ -948,14 +1013,14 @@
<div class="mb-3">
<div class="d-flex align-items-start mb-2">
<div class="me-2">👤</div>
<div class="bg-light rounded p-2 flex-grow-1">
<div class="bg-light rounded p-2 flex-grow-1" style="word-break: break-word;">
${chat.question}
</div>
</div>
<div class="d-flex align-items-start">
<div class="me-2">🤖</div>
<div class="bg-primary bg-opacity-10 rounded p-2 flex-grow-1">
<pre><code>${processMarkdownContent(chat.answer)}</code></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;"><code style="word-break: break-word;">${MarkdownContent(chat.answer)}</code></pre>
</div>
</div>
<div class="text-end text-muted small mt-1">
@@ -972,7 +1037,7 @@
}
}
function processMarkdownContent(content) {
function MarkdownContent(content) {
try {
// 如果内容是JSON字符串,先解析它
if (typeof content === 'string' && content.startsWith('"') && content.endsWith('"')) {
@@ -1002,7 +1067,7 @@
}
// Send question function
async function sendQuestion(referenceId) {
async function sendQuestion(paperId) {
const input = document.getElementById('questionInput');
const question = input.value.trim();
@@ -1023,7 +1088,7 @@
questionDiv.innerHTML = `
<div class="d-flex align-items-start mb-2">
<div class="me-2">👤</div>
<div class="bg-light rounded p-2 flex-grow-1">
<div class="bg-light rounded p-2 flex-grow-1" style="word-break: break-word;">
${question}
</div>
</div>
@@ -1047,12 +1112,12 @@
chatHistory.appendChild(loadingDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
const response = await fetch(`${API_BASE_URL}/qa`, {
const response = await fetch(`${API_BASE_URL}/${paperId}/qa`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ question, reference_id })
body: JSON.stringify({ question })
});
if (!response.ok) {
@@ -1088,7 +1153,7 @@
answerDiv.innerHTML = `
<div class="me-2">🤖</div>
<div class="bg-primary bg-opacity-10 rounded p-2 flex-grow-1">
<pre><code>${processMarkdownContent(result.answer)}</code></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;"><code style="word-break: break-word;">${MarkdownContent(result.answer)}</code></pre>
</div>
`;
@@ -1123,9 +1188,49 @@
// 添加显示文献列表的函数
function showPaperList() {
loadReferences();
// 显示文献列表页面
document.getElementById('DetailPage').classList.remove('hidden');
}
// 添加下载报告功能
function downloadReport() {
if (!currentReportData) {
alert('No report available to download');
return;
}
try {
// 使用原始的JSON数据创建下载内容
const downloadContent = JSON.stringify(currentReportData, null, 2);
// 创建Blob对象
const blob = new Blob([downloadContent], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
// 创建临时下载链接
const a = document.createElement('a');
a.href = url;
a.download = `paper_analysis_report_${new Date().toISOString().split('T')[0]}.json`;
// 触发下载
document.body.appendChild(a);
a.click();
// 清理
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('Failed to download report:', error);
alert('Failed to download report');
}
}
// 在页面加载完成后自动显示文献列表
document.addEventListener('DOMContentLoaded', function() {
showPaperList();
loadPapers();
});
</script>
</body>
</html>
+453 -287
View File
File diff suppressed because it is too large Load Diff