You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
16 KiB
390 lines
16 KiB
// 论文分析 JavaScript
|
|
|
|
// 将API密钥存储在会话存储中
|
|
let apiKey = sessionStorage.getItem('deepseekApiKey');
|
|
|
|
// DOM元素
|
|
const apiKeyInput = document.getElementById('apiKey');
|
|
const saveApiKeyBtn = document.getElementById('saveApiKey');
|
|
const fileInput = document.getElementById('paperFile');
|
|
const fileInfo = document.getElementById('fileInfo');
|
|
const analyzeBtn = document.getElementById('analyzePaper');
|
|
const resultsSection = document.querySelector('.results-section');
|
|
const tabButtons = document.querySelectorAll('.tab-btn');
|
|
const tabPanes = document.querySelectorAll('.tab-pane');
|
|
const exportBtn = document.getElementById('exportResults');
|
|
|
|
// 如果存在API密钥则初始化
|
|
if (apiKey) {
|
|
apiKeyInput.value = apiKey;
|
|
}
|
|
|
|
// 保存API密钥并测试连接
|
|
saveApiKeyBtn.addEventListener('click', async () => {
|
|
const newApiKey = apiKeyInput.value.trim();
|
|
if (!newApiKey) {
|
|
showNotification('请输入有效的API密钥', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showLoading('正在测试API连接...');
|
|
|
|
const response = await fetch('/paper-analysis/api/test-deepseek', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': newApiKey,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
sessionStorage.setItem('deepseekApiKey', newApiKey);
|
|
apiKey = newApiKey;
|
|
showNotification('API连接成功', 'success');
|
|
} else {
|
|
showNotification(`API错误: ${data.error || '未知错误'}`, 'error');
|
|
console.error('API错误详情:', data);
|
|
}
|
|
} catch (error) {
|
|
showNotification('测试API连接时出错: ' + error.message, 'error');
|
|
console.error('API测试错误:', error);
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
});
|
|
|
|
// 文件上传处理
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
document.querySelector('.file-upload-container').addEventListener('dragover', handleDragOver);
|
|
document.querySelector('.file-upload-container').addEventListener('drop', handleFileDrop);
|
|
|
|
function handleFileSelect(event) {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
updateFileInfo(file);
|
|
}
|
|
}
|
|
|
|
function handleDragOver(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.currentTarget.classList.add('drag-over');
|
|
}
|
|
|
|
function handleFileDrop(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.currentTarget.classList.remove('drag-over');
|
|
|
|
const file = event.dataTransfer.files[0];
|
|
if (file) {
|
|
fileInput.files = event.dataTransfer.files;
|
|
updateFileInfo(file);
|
|
}
|
|
}
|
|
|
|
function updateFileInfo(file) {
|
|
const sizeInMB = (file.size / (1024 * 1024)).toFixed(2);
|
|
fileInfo.innerHTML = `
|
|
<strong>文件:</strong> ${file.name}<br>
|
|
<strong>大小:</strong> ${sizeInMB} MB<br>
|
|
<strong>类型:</strong> ${file.type || '未知'}
|
|
`;
|
|
}
|
|
|
|
// 标签页导航
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const tabName = button.getAttribute('data-tab');
|
|
|
|
// 更新激活状态
|
|
tabButtons.forEach(btn => btn.classList.remove('active'));
|
|
tabPanes.forEach(pane => pane.classList.remove('active'));
|
|
|
|
button.classList.add('active');
|
|
document.getElementById(`${tabName}Tab`).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// 论文分析
|
|
analyzeBtn.addEventListener('click', async () => {
|
|
if (!apiKey) {
|
|
showNotification('请先配置您的Deepseek API密钥', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!fileInput.files[0]) {
|
|
showNotification('请选择要分析的文件', 'error');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('file', fileInput.files[0]);
|
|
formData.append('extract_keywords', document.getElementById('extractKeywords').checked);
|
|
formData.append('generate_summary', document.getElementById('generateSummary').checked);
|
|
formData.append('find_related', document.getElementById('findRelatedWorks').checked);
|
|
|
|
try {
|
|
showLoading('正在分析论文...');
|
|
|
|
const response = await fetch('/paper-analysis/api/analyze-paper', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-API-Key': apiKey
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (!response.ok) {
|
|
// 显示详细的错误信息
|
|
const errorMessage = responseData.error || `HTTP错误! 状态: ${response.status}`;
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
displayResults(responseData);
|
|
hideLoading();
|
|
resultsSection.style.display = 'block';
|
|
showNotification('分析完成成功', 'success');
|
|
} catch (error) {
|
|
hideLoading();
|
|
console.error('分析错误:', error);
|
|
showNotification('分析论文时出错: ' + error.message, 'error');
|
|
}
|
|
});
|
|
|
|
// 显示结果
|
|
function displayResults(results) {
|
|
// 显示关键词
|
|
const keywordsContainer = document.querySelector('.keywords-container');
|
|
if (results.keywords) {
|
|
keywordsContainer.innerHTML = results.keywords.map(keyword =>
|
|
`<div class="keyword-item">
|
|
<span class="keyword-text">${keyword.text}</span>
|
|
<span class="keyword-score">${(keyword.score * 100).toFixed(1)}%</span>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
|
|
// 显示摘要
|
|
const summaryContainer = document.querySelector('.summary-container');
|
|
if (results.summary) {
|
|
summaryContainer.innerHTML = `<div class="summary-text">${results.summary}</div>`;
|
|
}
|
|
|
|
// 显示相关工作
|
|
const relatedContainer = document.querySelector('.related-works-container');
|
|
if (results.related_works) {
|
|
// 如果尚未加载MathJax,则添加脚本
|
|
if (!window.MathJax) {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
|
|
script.async = true;
|
|
document.head.appendChild(script);
|
|
|
|
window.MathJax = {
|
|
tex: {
|
|
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
|
displayMath: [['$$', '$$'], ['\\[', '\\]']]
|
|
}
|
|
};
|
|
}
|
|
|
|
// 显示公式
|
|
relatedContainer.innerHTML = results.related_works.map((formula, index) => {
|
|
// 根据类型确定公式显示方式
|
|
let formulaDisplay = formula.formula;
|
|
|
|
// 为MathJax包装公式分隔符
|
|
if (!formulaDisplay.includes('$') && !formulaDisplay.includes('\\[') && !formulaDisplay.includes('\\(')) {
|
|
// 如果没有分隔符,则添加它们
|
|
if (formula.type === 'definition' || formula.type === 'theorem' || formula.importance > 0.7) {
|
|
formulaDisplay = `\\[${formulaDisplay}\\]`; // 为重要公式显示数学模式
|
|
} else {
|
|
formulaDisplay = `\\(${formulaDisplay}\\)`; // 为其他公式显示行内数学
|
|
}
|
|
}
|
|
|
|
// 确保变量格式正确以便显示
|
|
let variablesDisplay = '';
|
|
if (formula.variables) {
|
|
let englishVariables = '';
|
|
let chineseVariables = '';
|
|
|
|
// 处理英文变量
|
|
if (typeof formula.variables === 'string') {
|
|
try {
|
|
const variablesObj = JSON.parse(formula.variables);
|
|
englishVariables = Object.entries(variablesObj)
|
|
.map(([symbol, description]) => {
|
|
// 将数学符号包装在LaTeX分隔符中
|
|
const mathSymbol = `\\(${symbol}\\)`;
|
|
return `<div class="variable-item"><span class="variable-symbol">${mathSymbol}</span><span class="variable-separator">:</span><span class="variable-desc">${description}</span></div>`;
|
|
})
|
|
.join('');
|
|
} catch (e) {
|
|
englishVariables = `<div class="variable-item">${formula.variables}</div>`;
|
|
}
|
|
} else if (typeof formula.variables === 'object') {
|
|
englishVariables = Object.entries(formula.variables)
|
|
.map(([symbol, description]) => {
|
|
// 将数学符号包装在LaTeX分隔符中
|
|
const mathSymbol = `\\(${symbol}\\)`;
|
|
return `<div class="variable-item"><span class="variable-symbol">${mathSymbol}</span><span class="variable-separator">:</span><span class="variable-desc">${description}</span></div>`;
|
|
})
|
|
.join('');
|
|
} else {
|
|
englishVariables = `<div class="variable-item">${String(formula.variables)}</div>`;
|
|
}
|
|
|
|
// 如果可用,处理中文变量
|
|
if (formula.variables_chinese) {
|
|
if (typeof formula.variables_chinese === 'string') {
|
|
try {
|
|
const variablesChineseObj = JSON.parse(formula.variables_chinese);
|
|
chineseVariables = Object.entries(variablesChineseObj)
|
|
.map(([symbol, description]) => {
|
|
// 将数学符号包装在LaTeX分隔符中
|
|
const mathSymbol = `\\(${symbol}\\)`;
|
|
return `<div class="variable-item"><span class="variable-symbol">${mathSymbol}</span><span class="variable-separator">:</span><span class="variable-desc">${description}</span></div>`;
|
|
})
|
|
.join('');
|
|
} catch (e) {
|
|
chineseVariables = `<div class="variable-item">${formula.variables_chinese}</div>`;
|
|
}
|
|
} else if (typeof formula.variables_chinese === 'object') {
|
|
chineseVariables = Object.entries(formula.variables_chinese)
|
|
.map(([symbol, description]) => {
|
|
// 将数学符号包装在LaTeX分隔符中
|
|
const mathSymbol = `\\(${symbol}\\)`;
|
|
return `<div class="variable-item"><span class="variable-symbol">${mathSymbol}</span><span class="variable-separator">:</span><span class="variable-desc">${description}</span></div>`;
|
|
})
|
|
.join('');
|
|
}
|
|
}
|
|
|
|
if (chineseVariables) {
|
|
variablesDisplay = `
|
|
<div class="variables-tabs">
|
|
<button class="var-tab-btn active" onclick="switchVariableTab(this, 'english')">English</button>
|
|
<button class="var-tab-btn" onclick="switchVariableTab(this, 'chinese')">中文</button>
|
|
</div>
|
|
<div class="variables-content">
|
|
<div class="variables-list english-vars active">${englishVariables}</div>
|
|
<div class="variables-list chinese-vars">${chineseVariables}</div>
|
|
</div>`;
|
|
} else {
|
|
variablesDisplay = `<div class="variables-list">${englishVariables}</div>`;
|
|
}
|
|
}
|
|
|
|
return `<div class="formula-item">
|
|
<div class="formula-header">
|
|
<span class="formula-number">#${index + 1}</span>
|
|
<span class="formula-type ${formula.type}">${formula.type.toUpperCase()}</span>
|
|
</div>
|
|
<div class="formula-expression">${formulaDisplay}</div>
|
|
<div class="formula-description">${formula.description}</div>
|
|
${variablesDisplay ? `<div class="formula-variables"><strong>变量:</strong><div class="variables-list">${variablesDisplay}</div></div>` : ''}
|
|
<div class="formula-context"><strong>上下文:</strong> ${formula.context}</div>
|
|
<div class="formula-chinese"><strong>中文描述:</strong> ${formula.chinese_description || formula.Chinese_description || '无中文描述'}</div>
|
|
</div>`;
|
|
}).join('');
|
|
|
|
// 触发MathJax处理公式
|
|
if (window.MathJax && window.MathJax.typesetPromise) {
|
|
window.MathJax.typesetPromise([relatedContainer]).catch((e) => console.error(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 导出结果
|
|
exportBtn.addEventListener('click', () => {
|
|
const results = {
|
|
keywords: Array.from(document.querySelectorAll('.keyword-item')).map(item => ({
|
|
text: item.querySelector('.keyword-text').textContent,
|
|
score: parseFloat(item.querySelector('.keyword-score').textContent) / 100
|
|
})),
|
|
summary: document.querySelector('.summary-text')?.textContent,
|
|
related_works: Array.from(document.querySelectorAll('.formula-item')).map(item => {
|
|
const variablesElement = item.querySelector('.formula-variables');
|
|
const contextElement = item.querySelector('.formula-context');
|
|
const chineseElement = item.querySelector('.formula-chinese');
|
|
|
|
return {
|
|
formula: item.querySelector('.formula-expression').textContent,
|
|
type: item.querySelector('.formula-type').textContent.toLowerCase(),
|
|
description: item.querySelector('.formula-description').textContent,
|
|
variables: variablesElement ? variablesElement.textContent.replace('变量: ', '') : '',
|
|
context: contextElement ? contextElement.textContent.replace('上下文: ', '') : '',
|
|
chinese_description: chineseElement ? chineseElement.textContent.replace('中文描述: ', '') : ''
|
|
};
|
|
})
|
|
};
|
|
|
|
const blob = new Blob([JSON.stringify(results, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'paper_analysis_results.json';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
});
|
|
|
|
// 工具函数
|
|
function showNotification(message, type) {
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.textContent = message;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 3000);
|
|
}
|
|
|
|
let loadingElement = null;
|
|
|
|
function showLoading(message) {
|
|
loadingElement = document.createElement('div');
|
|
loadingElement.className = 'loading-overlay';
|
|
loadingElement.innerHTML = `
|
|
<div class="loading-spinner"></div>
|
|
<div class="loading-message">${message}</div>
|
|
`;
|
|
document.body.appendChild(loadingElement);
|
|
}
|
|
|
|
function hideLoading() {
|
|
if (loadingElement) {
|
|
loadingElement.remove();
|
|
loadingElement = null;
|
|
}
|
|
}
|
|
|
|
// 在英文和中文变量描述之间切换的函数
|
|
function switchVariableTab(button, language) {
|
|
const variablesContainer = button.closest('.formula-variables');
|
|
const tabs = variablesContainer.querySelectorAll('.var-tab-btn');
|
|
const contents = variablesContainer.querySelectorAll('.variables-list');
|
|
|
|
// 更新标签按钮
|
|
tabs.forEach(tab => tab.classList.remove('active'));
|
|
button.classList.add('active');
|
|
|
|
// 更新内容可见性
|
|
contents.forEach(content => content.classList.remove('active'));
|
|
const targetContent = variablesContainer.querySelector(`.${language}-vars`);
|
|
if (targetContent) {
|
|
targetContent.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 使函数全局化,以便可以从onclick调用
|
|
window.switchVariableTab = switchVariableTab; |