-
${basicInfo.title}
-
- Author: ${basicInfo.author}
- Publication Date: ${basicInfo.publication_date}
- Journal/Publisher: ${basicInfo.journal_publisher}
-
+
${reference.reference_title}
+
+
+
+ Loading...
+
+
Analyzing literature content...
+
+
`;
@@ -625,6 +619,7 @@
const referencesList = document.getElementById('referenceAnalysis');
try {
+ // Get literature list
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/references`, {
headers: {
'Authorization': `Bearer ${token}`
@@ -632,7 +627,6 @@
});
if (response.status === 401) {
- // Token 无效或过期
window.location.href = 'https://beta.obscura.work/lab/login.html';
return;
}
@@ -652,27 +646,93 @@
referencesList.innerHTML = '
No literature data, please upload literature
';
return;
}
-
- const referencesWithAnalysis = await Promise.all(references.map(async (ref) => {
+
+ // Render literature cards
+ const referenceCards = references.map(ref => renderReferenceCard(ref, projectId)).join('');
+ referencesList.innerHTML = referenceCards;
+
+ // Get analysis status and results for each literature
+ references.forEach(async (ref) => {
try {
const analysisResponse = await fetch(`${API_BASE_URL}/references/${ref._id}/report`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
-
- if (analysisResponse.ok) {
- const analysis = await analysisResponse.json();
- return renderReferenceCard(ref, projectId, analysis);
+
+ if (!analysisResponse.ok) {
+ throw new Error('Failed to fetch analysis');
+ }
+
+ const analysisResult = await analysisResponse.json();
+ const contentElement = document.querySelector(`.analysis-content-${ref._id}`);
+ const statusElement = document.querySelector(`.analysis-status-${ref._id}`);
+
+ if (analysisResult.status === 'processing') {
+ // Processing status
+ contentElement.innerHTML = `
+
+
+ Loading...
+
+
Analyzing literature content...
+
+ `;
+ statusElement.innerHTML = '
Analysis in progress...';
+ } else if (analysisResult.status === 'completed') {
+ // Completed status
+ let author = 'N/A';
+ let publicationDate = 'N/A';
+ let journal = 'N/A';
+
+ try {
+ if (analysisResult['1. Basic Information']) {
+ const basicInfo = analysisResult['1. Basic Information'];
+ author = basicInfo.author || 'N/A';
+ publicationDate = basicInfo.publication_date || 'N/A';
+ journal = basicInfo.journal_publisher || 'N/A';
+
+ // 更新卡片标题为分析结果中的标题
+ const titleElement = document.querySelector(`#reference-${ref._id} .card-title`);
+ if (titleElement && basicInfo.title) {
+ titleElement.textContent = basicInfo.title;
+ }
+ }
+
+ contentElement.innerHTML = `
+
Author: ${author}
+
Publication Date: ${publicationDate}
+
Journal/Publisher: ${journal}
+ `;
+ statusElement.innerHTML = '
Analysis completed';
+ } catch (parseError) {
+ console.error('Failed to parse analysis result:', parseError);
+ }
+ } else if (analysisResult.status === 'failed') {
+ // Failed status
+ contentElement.innerHTML = `
+
+
+ Analysis failed
+
+ `;
+ statusElement.innerHTML = `
Analysis failed: ${analysisResult.message || 'Unknown error'}`;
}
} catch (error) {
- console.error(`Failed to get analysis report for literature ${ref._id}:`, error);
+ console.error(`Failed to fetch analysis for reference ${ref._id}:`, error);
+ const contentElement = document.querySelector(`.analysis-content-${ref._id}`);
+ const statusElement = document.querySelector(`.analysis-status-${ref._id}`);
+
+ contentElement.innerHTML = `
+
+
+ Failed to get analysis status
+
+ `;
+ statusElement.innerHTML = '
Failed to get analysis status';
}
- return renderReferenceCard(ref, projectId);
- }));
-
- referencesList.innerHTML = referencesWithAnalysis.join('');
-
+ });
+
} catch (error) {
console.error('Failed to load literature list:', error);
referencesList.innerHTML = `
@@ -686,7 +746,6 @@
}
}
- // Upload literature function
async function uploadLiterature() {
if (!currentProjectId) {
alert('Please select a project first');
@@ -702,12 +761,10 @@
const files = Array.from(e.target.files);
if (!files.length) return;
- // 创建进度条卡片
+ // 创建进度条
const progressCard = document.createElement('div');
progressCard.className = 'card w-100 mb-4';
- progressCard.style.cssText = `
- transition: all 0.3s ease;
- `;
+ progressCard.style.cssText = `transition: all 0.3s ease;`;
const progressCardBody = document.createElement('div');
progressCardBody.className = 'card-body';
@@ -746,80 +803,56 @@
const titleElement = document.getElementById('referenceAnalysisTitle');
titleElement.parentNode.parentNode.insertBefore(progressCard, titleElement.parentNode);
- const updateProgress = (current, total, status) => {
- const progress = (current / total) * 100;
- progressFill.style.width = `${progress}%`;
- progressText.textContent = `${status} (${current}/${total})`;
- };
-
- const removeProgressBar = () => {
- progressCard.style.opacity = '0';
- progressCard.style.transform = 'translateY(-10px)';
- setTimeout(() => {
- progressCard.remove();
- }, 300);
- };
-
try {
- for (let i = 0; i < files.length; i++) {
- const file = files[i];
- const formData = new FormData();
- formData.append('file', file);
- formData.append('reference_title', file.name);
+ const formData = new FormData();
+ files.forEach(file => {
+ formData.append('files', file);
+ });
- try {
- updateProgress(i + 1, files.length, `Uploading: ${file.name}`);
- const uploadResponse = await fetch(`${API_BASE_URL}/projects/${currentProjectId}/references`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`
- },
- body: formData
- });
-
- if (!uploadResponse.ok) {
- throw new Error((await uploadResponse.json()).detail || 'Upload failed');
- }
-
- const uploadResult = await uploadResponse.json();
- const referenceId = uploadResult.reference_id;
-
- updateProgress(i + 1, files.length, `Analyzing: ${file.name}`);
- const analyzeResponse = await fetch(`${API_BASE_URL}/references/${referenceId}/analyze`, {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!analyzeResponse.ok) {
- throw new Error((await analyzeResponse.json()).detail || 'Analysis failed');
- }
-
- const reportResponse = await fetch(`${API_BASE_URL}/references/${referenceId}/report`, {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!reportResponse.ok) {
- throw new Error('Failed to get analysis report');
- }
-
- } catch (error) {
- console.error(`Error processing ${file.name}:`, error);
+ // 使用 XMLHttpRequest 来获取上传进度
+ const xhr = new XMLHttpRequest();
+
+ xhr.upload.onprogress = function(e) {
+ if (e.lengthComputable) {
+ const percentComplete = (e.loaded / e.total) * 100;
+ progressFill.style.width = percentComplete + '%';
+ progressText.textContent = `Uploading: ${Math.round(percentComplete)}%`;
}
- }
+ };
+ const uploadPromise = new Promise((resolve, reject) => {
+ xhr.onload = function() {
+ if (xhr.status === 200) {
+ resolve(JSON.parse(xhr.response));
+ } else {
+ reject(new Error('Upload failed'));
+ }
+ };
+ xhr.onerror = () => reject(new Error('Upload failed'));
+ });
+
+ xhr.open('POST', `${API_BASE_URL}/projects/${currentProjectId}/references/batch`);
+ xhr.setRequestHeader('Authorization', `Bearer ${token}`);
+ xhr.send(formData);
+
+ await uploadPromise;
+
+ // 上传完成后移除进度条
setTimeout(() => {
- removeProgressBar();
- }, 1000);
+ progressCard.style.opacity = '0';
+ progressCard.style.transform = 'translateY(-10px)';
+ setTimeout(() => {
+ progressCard.remove();
+ }, 300);
+ }, 500);
+ // 刷新文献列表
await loadReferences(currentProjectId);
} catch (error) {
- console.error('Upload process failed:', error);
+ console.error('Upload failed:', error);
progressCard.remove();
- progressText.remove();
+ alert(`Upload failed: ${error.message}`);
}
};
@@ -827,79 +860,104 @@
}
// View literature details function
- async function viewReferenceDetail(referenceId, encodedAnalysis = '') {
+ async function viewReferenceDetail(referenceId) {
try {
- let analysisResult;
-
- if (encodedAnalysis) {
- try {
- analysisResult = JSON.parse(decodeURIComponent(encodedAnalysis));
- } catch (e) {
- console.error('Failed to parse encoded analysis:', e);
- analysisResult = null;
+ const response = await fetch(`${API_BASE_URL}/references/${referenceId}/report`, {
+ headers: {
+ 'Authorization': `Bearer ${token}`
}
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch analysis report');
}
- if (!analysisResult) {
- const response = await fetch(`${API_BASE_URL}/references/${referenceId}/report`, {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
+ const analysisResult = await response.json();
+ let modalContent;
+
+ if (analysisResult.status === 'processing') {
+ modalContent = `
+
+
+ Loading...
+
+
Analyzing literature content, please wait...
+
+ `;
+ } else if (analysisResult.status === 'completed') {
+ const formattedReport = JSON.stringify(analysisResult, null, 2)
+ .replace(/[{}"]/g, '')
+ .replace(/},/g, '')
+ .replace(/{ },/g, '')
+ .replace(/^\s*,/gm, '')
+ .replace(/Literature Analysis Report:/, '
Literature Analysis Report
')
+ .replace(/^(\s*\d+\.[^:\n]+:)/gm, '
$1')
+ .split('\n')
+ .map(line => line.trimEnd())
+ .join('\n');
- if (!response.ok) {
- throw new Error('Failed to get analysis report');
- }
-
- analysisResult = await response.json();
+ modalContent = `
+
${formattedReport}
+ `;
+ } else if (analysisResult.status === 'failed') {
+ modalContent = `
+
+
+ Analysis failed: ${analysisResult.message || 'Unknown error'}
+
+ `;
}
-
- const formattedReport = JSON.stringify(analysisResult, null, 2)
- .replace(/[{}"]/g, '')
- .replace(/},/g, '')
- .replace(/{ },/g, '')
- .replace(/^\s*,/gm, '')
- .replace(/Literature Analysis Report:/, '
Literature Analysis Report
')
- .replace(/^(\s*\d+\.[^:\n]+:)/gm, '
$1')
- .split('\n')
- .map(line => line.trimEnd())
- .join('\n');
-
+
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'referenceDetailModal';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-labelledby', 'referenceDetailModalTitle');
+
modal.innerHTML = `
-
-
+
+
-
-
${formattedReport}
+
+ ${modalContent}
`;
document.body.appendChild(modal);
- const modalInstance = new bootstrap.Modal(modal);
+ const modalInstance = new bootstrap.Modal(modal, {
+ keyboard: true,
+ focus: true
+ });
+
modalInstance.show();
- modal.addEventListener('hidden.bs.modal', () => {
- document.body.removeChild(modal);
+ modal.addEventListener('hide.bs.modal', () => {
+ const focusedElement = document.activeElement;
+ if (modal.contains(focusedElement)) {
+ focusedElement.blur();
+ }
});
+
+ modal.addEventListener('hidden.bs.modal', () => {
+ modal.remove();
+ });
+
} catch (error) {
console.error('Failed to view details:', error);
- alert('Failed to get analysis report, please try again');
+ alert('Failed to get literature analysis report, please try again later');
}
}
// View literature details from card
function viewReferenceDetailFromCard(card) {
const referenceId = card.id.replace('reference-', '');
- const encodedAnalysis = card.getAttribute('data-analysis') || '';
- viewReferenceDetail(referenceId, encodedAnalysis);
+ viewReferenceDetail(referenceId);
}
// Download literature analysis report function
@@ -1015,25 +1073,29 @@
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'chatModal';
- modal.setAttribute('data-bs-backdrop', 'static');
- modal.setAttribute('data-bs-keyboard', 'false');
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-labelledby', 'chatModalTitle');
+
modal.innerHTML = `
-
+
+ placeholder="Type your question here..."
+ aria-label="Question input">
+ onclick="sendQuestion('${referenceId}')"
+ aria-label="Send question">
Send
@@ -1043,23 +1105,45 @@
`;
document.body.appendChild(modal);
- const modalInstance = new bootstrap.Modal(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();
- // Load chat history
+ // 加载聊天历史
await loadChatHistory(referenceId);
- // Add event listener for Enter key
+ // 添加回车键事件监听器
const input = document.getElementById('questionInput');
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendQuestion(referenceId);
}
});
-
- modal.addEventListener('hidden.bs.modal', () => {
- document.body.removeChild(modal);
- });
}
// Load chat history function
@@ -1122,11 +1206,13 @@
}
const chatHistory = document.getElementById('chatHistory');
+ const sendButton = input.nextElementSibling;
try {
- // Clear input and disable it
+ // 清空输入并禁用输入框和发送按钮
input.value = '';
input.disabled = true;
+ sendButton.disabled = true;
// First, add the user's question to chat history
const questionDiv = document.createElement('div');
@@ -1236,8 +1322,13 @@
chatHistory.appendChild(errorDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
} finally {
+ // 重新启用输入框和发送按钮
input.disabled = false;
- input.focus();
+ sendButton.disabled = false;
+ // 将焦点返回到输入框,但不强制聚焦
+ if (document.activeElement === document.body) {
+ input.focus();
+ }
}
}
diff --git a/main.py b/main.py
index 2793060..1ee4b62 100644
--- a/main.py
+++ b/main.py
@@ -24,6 +24,9 @@ import PyPDF2
import numpy as np
import zipfile
from io import BytesIO, StringIO
+import aiohttp
+from concurrent.futures import ThreadPoolExecutor
+from functools import partial
# Database Configuration
MONGODB_URL = "mongodb://lab:y6aHwySAhzrbibLD@222.186.10.253:27017/lab"
@@ -1603,7 +1606,7 @@ async def analyze_project_data(project_data):
print(f"Error calling DeepSeek API: {e}")
return None
-async def analyze_reference_document(content: str):
+async def analyze_reference_document_async(content: str):
"""分析文献的基本信息和内容"""
system_prompt = """
You are an AI assistant tasked with analyzing academic literature.
@@ -1635,23 +1638,12 @@ async def analyze_reference_document(content: str):
}}
"""
- messages = [
+ return await call_deepseek_api_async([
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
- ]
-
- try:
- response = client.chat.completions.create(
- model="deepseek-chat",
- messages=messages,
- response_format={'type': 'json_object'}
- )
- return json.loads(response.choices[0].message.content)
- except Exception as e:
- print(f"Error calling DeepSeek API for document analysis: {e}")
- return None
+ ])
-async def analyze_reference_value(content_analysis: dict):
+async def analyze_reference_value_async(content_analysis: dict):
"""基于内容分析结果评估文献的价值"""
system_prompt = """
You are an AI assistant tasked with evaluating the value of academic literature based on its content analysis.
@@ -1674,21 +1666,10 @@ async def analyze_reference_value(content_analysis: dict):
}}
"""
- messages = [
+ return await call_deepseek_api_async([
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
- ]
-
- try:
- response = client.chat.completions.create(
- model="deepseek-chat",
- messages=messages,
- response_format={'type': 'json_object'}
- )
- return json.loads(response.choices[0].message.content)
- except Exception as e:
- print(f"Error calling DeepSeek API for value evaluation: {e}")
- return None
+ ])
# 修改函数定义为异步函数
async def analyze_reference_summary(reference_data):
@@ -2059,70 +2040,6 @@ class ReferenceModel(BaseModel):
arbitrary_types_allowed = True
json_encoders = {ObjectId: str}
-# 添加文献上传路由
-@app.post("/lab/projects/{project_id}/references")
-async def upload_reference(
- project_id: str,
- file: UploadFile = File(...),
- reference_title: str = Form(...),
- current_user: UserModel = Depends(get_current_user)
-):
- """上传项目相关文献"""
- db = await get_database()
-
- try:
- # 验证项目是否存在且属于当前用户
- project = await db.projects.find_one({
- "_id": ObjectId(project_id),
- "user_id": current_user.id
- })
- if not project:
- raise HTTPException(status_code=404, detail="Project not found or unauthorized access")
-
- # 验证文件类型(可选)
- allowed_types = ["application/pdf", "application/msword",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]
- if file.content_type not in allowed_types:
- raise HTTPException(status_code=400, detail="Unsupported file type")
-
- # 确保上传目录存在
- os.makedirs(upload_path, exist_ok=True)
-
- # 生成安全的文件名
- file_extension = os.path.splitext(file.filename)[1]
- safe_filename = f"{project_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{file_extension}"
- file_path = os.path.join(upload_path, safe_filename)
-
- # 保存文件
- with open(file_path, "wb") as buffer:
- content = await file.read()
- buffer.write(content)
-
- # 创建引用记录
- reference = {
- "project_id": ObjectId(project_id),
- "reference_link": file_path,
- "reference_title": reference_title or file.filename, # 使用提供的标题或文件名
- "upload_time": datetime.now(timezone.utc)
- }
-
- result = await db.references.insert_one(reference)
-
- return {
- "message": "Reference uploaded successfully",
- "reference_id": str(result.inserted_id),
- "file_path": file_path,
- "reference_title": reference["reference_title"]
- }
-
- except Exception as e:
- print(f"Detailed error: {str(e)}") # 添加详细错误日志
- if 'file_path' in locals() and os.path.exists(file_path):
- os.remove(file_path)
- raise HTTPException(
- status_code=400,
- detail=f"Failed to upload reference: {str(e)}"
- )
# 删除文献
@app.delete("/lab/projects/{project_id}/references/{reference_id}")
@@ -2221,89 +2138,6 @@ async def get_reference_report(
except Exception as e:
print(f"Error closing Redis connection: {e}")
-@app.get("/lab/references/{reference_id}/analyze")
-async def analyze_reference_data(
- reference_id: str,
- current_user: UserModel = Depends(get_current_user)
-):
- """分析文献数据"""
- db = await get_database()
- redis = await get_redis()
-
- try:
- # 从数据库获取文献信息
- reference = await db.references.find_one({"_id": ObjectId(reference_id)})
- if not reference:
- raise HTTPException(status_code=404, detail="Reference record not found")
-
- file_path = reference.get("reference_link")
- if not file_path or not os.path.exists(file_path):
- raise HTTPException(status_code=404, detail="Reference file not found")
-
- # 读取PDF文件内容
- pdf_content = ""
- try:
- with open(file_path, 'rb') as file:
- pdf_reader = PyPDF2.PdfReader(file)
- for page in pdf_reader.pages:
- content = page.extract_text()
- pdf_content += content
- except Exception as e:
- print(f"Error reading PDF file: {str(e)}")
- raise HTTPException(status_code=500, detail="Failed to read PDF file")
-
- # 限制内容长度
- MAX_CHARS = 180000
- pdf_content = pdf_content[:MAX_CHARS]
- # 分析文档内容
- document_analysis = await analyze_reference_document(pdf_content)
- if not document_analysis:
- raise HTTPException(status_code=500, detail="Failed to analyze document")
-
- # 分析文献价值
- value_evaluation = await analyze_reference_value(document_analysis)
- if not value_evaluation:
- raise HTTPException(status_code=500, detail="Failed to evaluate value")
-
- # 合并所有分析结果
- analysis_result = {
- **document_analysis,
- **value_evaluation
- }
-
- # 将分析结果保存到db203
- await redis.select(203)
- report_key = f"reference_report:{reference_id}"
- await redis.set(report_key, json.dumps(analysis_result))
-
- # 更新数据库中的分析状态
- await db.references.update_one(
- {"_id": ObjectId(reference_id)},
- {"$set": {
- "analysis_status": "completed"
- }}
- )
-
- return analysis_result
-
- except Exception as e:
- print(f"Error analyzing reference data: {str(e)}")
- # 更新数据库中的分析状态为失败
- if 'reference' in locals():
- await db.references.update_one(
- {"_id": ObjectId(reference_id)},
- {"$set": {
- "analysis_status": "failed",
- "analysis_error": str(e),
- }}
- )
- raise HTTPException(status_code=500, detail=str(e))
- finally:
- try:
- await redis.aclose()
- except Exception as e:
- print(f"Error closing Redis connection: {e}")
-
@app.get("/lab/references/{project_id}/analyze_report")
async def analyze_reference_summary_report(
project_id: str,
@@ -2834,6 +2668,258 @@ async def get_reference_qa_history(
except Exception as e:
print(f"Error closing Redis connection: {e}")
+@app.post("/lab/projects/{project_id}/references/batch")
+async def batch_upload_references(
+ project_id: str,
+ files: List[UploadFile] = File(...),
+ current_user: UserModel = Depends(get_current_user)
+):
+ """批量上传项目相关文献"""
+ db = await get_database()
+
+ try:
+ # 验证项目是否存在且属于当前用户
+ project = await db.projects.find_one({
+ "_id": ObjectId(project_id),
+ "user_id": current_user.id
+ })
+ if not project:
+ raise HTTPException(status_code=404, detail="Project not found or unauthorized access")
+
+ uploaded_references = []
+
+ # 批量上传文件
+ for file in files:
+ # 验证文件类型
+ allowed_types = ["application/pdf", "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]
+ if file.content_type not in allowed_types:
+ continue # 跳过不支持的文件类型
+
+ # 确保上传目录存在
+ os.makedirs(upload_path, exist_ok=True)
+
+ # 生成安全的文件名
+ file_extension = os.path.splitext(file.filename)[1]
+ safe_filename = f"{project_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{file_extension}"
+ file_path = os.path.join(upload_path, safe_filename)
+
+ # 保存文件
+ with open(file_path, "wb") as buffer:
+ content = await file.read()
+ buffer.write(content)
+
+ # 创建引用记录
+ reference = {
+ "project_id": ObjectId(project_id),
+ "reference_link": file_path,
+ "reference_title": file.filename,
+ "upload_time": datetime.now(timezone.utc)
+ }
+
+ result = await db.references.insert_one(reference)
+ reference_info = {
+ "reference_id": str(result.inserted_id),
+ "file_path": file_path,
+ "reference_title": reference["reference_title"]
+ }
+ uploaded_references.append(reference_info)
+
+ # 为每个文献创建初始状态
+ redis = await get_redis()
+ try:
+ await redis.select(203)
+ report_key = f"reference_report:{str(result.inserted_id)}"
+ initial_status = {
+ "status": "processing",
+ "message": "Analysis in progress"
+ }
+ await redis.set(report_key, json.dumps(initial_status))
+ finally:
+ await redis.aclose()
+
+ # 在后台启动分析任务
+ if uploaded_references:
+ asyncio.create_task(process_batch_analysis(uploaded_references))
+
+ return {
+ "message": f"Successfully uploaded {len(uploaded_references)} files",
+ "uploaded_files": uploaded_references
+ }
+
+ except Exception as e:
+ print(f"Batch upload error: {str(e)}")
+ raise HTTPException(status_code=500, detail=str(e))
+
+async def process_batch_analysis(references: List[dict]):
+ """批量处理文献分析的后台任务"""
+ redis = await get_redis()
+
+ # 限制并发数量
+ semaphore = asyncio.Semaphore(3)
+
+ async def process_single_reference(ref: dict):
+ async with semaphore:
+ try:
+ reference_id = ref["reference_id"]
+ file_path = ref["file_path"]
+
+ if not os.path.exists(file_path):
+ print(f"Reference file not found: {file_path}")
+ return
+
+ # 异步读取PDF
+ pdf_content = await read_pdf_async(file_path)
+ if not pdf_content:
+ raise Exception("Failed to read PDF content")
+
+ # 异步分析文档
+ document_analysis = await analyze_reference_document_async(pdf_content)
+ if not document_analysis:
+ raise Exception("Failed to analyze document")
+
+ # 等待一小段时间避免API限制
+ await asyncio.sleep(1)
+
+ # 异步分析价值
+ value_evaluation = await analyze_reference_value_async(document_analysis)
+ if not value_evaluation:
+ raise Exception("Failed to evaluate value")
+
+ # 合并结果
+ analysis_result = {
+ **document_analysis,
+ **value_evaluation,
+ "status": "completed"
+ }
+
+ # 保存结果
+ await redis.select(203)
+ report_key = f"reference_report:{reference_id}"
+ await redis.set(report_key, json.dumps(analysis_result))
+
+ except Exception as e:
+ print(f"Error processing reference {ref['reference_id']}: {str(e)}")
+ try:
+ await redis.select(203)
+ report_key = f"reference_report:{ref['reference_id']}"
+ error_status = {
+ "status": "failed",
+ "message": str(e)
+ }
+ await redis.set(report_key, json.dumps(error_status))
+ except Exception as redis_error:
+ print(f"Error updating Redis status: {redis_error}")
+
+ try:
+ # 并发处理所有引用
+ await asyncio.gather(
+ *(process_single_reference(ref) for ref in references)
+ )
+ finally:
+ try:
+ await redis.aclose()
+ except Exception as e:
+ print(f"Error closing Redis connection: {e}")
+
+# 在全局范围创建线程池
+pdf_thread_pool = ThreadPoolExecutor(max_workers=3) # 限制并发PDF处理数量
+
+# 创建异步HTTP客户端会话
+async def get_aiohttp_session():
+ return aiohttp.ClientSession(
+ base_url="https://api.deepseek.com/v1/", # 添加了末尾的斜杠
+ headers={"Authorization": f"Bearer sk-3027fb3c810b4e17985fa397d41250b9"}
+ )
+
+async def read_pdf_async(file_path: str) -> str:
+ """在线程池中异步读取PDF"""
+ def read_pdf():
+ try:
+ pdf_content = ""
+ with open(file_path, 'rb') as file:
+ pdf_reader = PyPDF2.PdfReader(file)
+ for page in pdf_reader.pages:
+ content = page.extract_text()
+ pdf_content += content
+ return pdf_content[:180000] # 限制内容长度
+ except Exception as e:
+ print(f"Error reading PDF: {e}")
+ return ""
+
+ loop = asyncio.get_event_loop()
+ return await loop.run_in_executor(pdf_thread_pool, read_pdf)
+
+async def call_deepseek_api_async(messages: list) -> dict:
+ """异步调用DeepSeek API"""
+ async with await get_aiohttp_session() as session:
+ async with session.post("/chat/completions", json={
+ "model": "deepseek-chat",
+ "messages": messages,
+ "response_format": {"type": "json_object"}
+ }) as response:
+ if response.status == 200:
+ data = await response.json()
+ return json.loads(data["choices"][0]["message"]["content"])
+ else:
+ raise Exception(f"API调用失败: {await response.text()}")
+
+async def process_reference_analysis(reference_id: str, reference: dict):
+ """后台处理文献分析"""
+ redis = await get_redis()
+
+ try:
+ file_path = reference.get("reference_link")
+ if not file_path or not os.path.exists(file_path):
+ print(f"Reference file not found: {file_path}")
+ return
+
+ # 异步读取PDF
+ pdf_content = await read_pdf_async(file_path)
+ if not pdf_content:
+ raise Exception("Failed to read PDF content")
+
+ # 异步分析文档内容
+ document_analysis = await analyze_reference_document_async(pdf_content)
+ if not document_analysis:
+ print("Failed to analyze document")
+ return
+
+ # 异步分析文献价值
+ value_evaluation = await analyze_reference_value_async(document_analysis)
+ if not value_evaluation:
+ print("Failed to evaluate value")
+ return
+
+ # 合并分析结果
+ analysis_result = {
+ **document_analysis,
+ **value_evaluation
+ }
+
+ # 保存分析结果到db203
+ await redis.select(203)
+ report_key = f"reference_report:{reference_id}"
+ await redis.set(report_key, json.dumps(analysis_result))
+
+ except Exception as e:
+ print(f"Error analyzing reference data: {str(e)}")
+ try:
+ await redis.select(203)
+ report_key = f"reference_report:{reference_id}"
+ error_status = {
+ "status": "failed",
+ "message": str(e)
+ }
+ await redis.set(report_key, json.dumps(error_status))
+ except Exception as redis_error:
+ print(f"Error updating Redis status: {redis_error}")
+ finally:
+ try:
+ await redis.aclose()
+ except Exception as e:
+ print(f"Error closing Redis connection: {e}")
+
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=6000)
\ No newline at end of file