feat: add concurrency safety, caption detection, admin enhancements, and performance improvements

This commit is contained in:
2026-06-14 22:20:02 +08:00
parent 8f13c31991
commit 29fb20828e
23 changed files with 1782 additions and 114 deletions
+17
View File
@@ -29,6 +29,7 @@
<option value="title_asc" {% if current_sort == 'title_asc' %}selected{% endif %}>标题 A→Z</option>
</select>
<button type="submit" class="paper-search-btn">搜索</button>
<a class="admin-action-btn admin-action-btn-sm" href="/admin/papers/export.csv{% if request.query_params %}?{{ request.query_params }}{% endif %}">⬇ 导出 CSV</a>
</div>
</form>
@@ -37,6 +38,7 @@
<span class="paper-batch-label">批量操作</span>
<span class="paper-selected-count" id="selected-count">已选 0 篇</span>
<button class="admin-action-btn admin-action-btn-sm" onclick="batchAction('summarize')" id="batch-summarize-btn" disabled>📝 批量总结</button>
<button class="admin-action-btn admin-action-btn-sm" onclick="batchAction('recrawl')" id="batch-recrawl-btn" disabled>🔄 批量重抓</button>
<button class="admin-action-btn admin-action-btn-sm admin-action-btn-danger" onclick="batchAction('delete')" id="batch-delete-btn" disabled>🗑 批量删除</button>
</div>
@@ -72,6 +74,7 @@
</td>
<td class="action-cell">
<button class="action-btn-sm" title="重新总结" onclick="retryOne('{{ paper.arxiv_id }}', this)"></button>
<button class="action-btn-sm" title="重新抓取元数据" onclick="recrawlOne('{{ paper.arxiv_id }}', this)">🔄</button>
<button class="action-btn-sm action-btn-danger" title="删除" onclick="confirmDeleteSingle('{{ paper.arxiv_id }}', '{{ (paper.title_zh or paper.title_en)[:40] | replace("'", "\\'") }}')">🗑</button>
</td>
</tr>
@@ -124,6 +127,7 @@
const n=document.querySelectorAll('.paper-check:checked').length;
document.getElementById('selected-count').textContent='已选 '+n+' 篇';
document.getElementById('batch-summarize-btn').disabled=n===0;
document.getElementById('batch-recrawl-btn').disabled=n===0;
document.getElementById('batch-delete-btn').disabled=n===0;
}
function retryOne(arxivId,btn) {
@@ -134,6 +138,14 @@
.catch(()=>showToast('❌ 请求失败'))
.finally(()=>{btn.disabled=false;btn.textContent='↻';});
}
function recrawlOne(arxivId,btn) {
btn.disabled=true;btn.textContent='...';
fetch('/admin/paper-recrawl/'+arxivId,{method:'POST',headers:{'Content-Type':'application/json'}})
.then(r=>r.json())
.then(data=>showToast(data.error?'❌ '+data.error.substring(0,100):'✅ 重抓任务已创建,可在任务页查看'))
.catch(()=>showToast('❌ 请求失败'))
.finally(()=>{btn.disabled=false;btn.textContent='🔄';});
}
function confirmDeleteSingle(arxivId,title) {
document.getElementById('confirm-msg').textContent='确定删除论文「'+title+'」?此操作不可恢复。';
_confirmAction='delete-single'; _confirmTarget=arxivId;
@@ -151,6 +163,11 @@
.then(r=>r.json())
.then(data=>showToast(data.error?'❌ '+data.error.substring(0,100):'✅ 已提交批量总结'))
.catch(()=>showToast('❌ 请求失败'));
} else if(action==='recrawl'){
fetch('/admin/papers-batch-action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'recrawl',arxiv_ids:ids})})
.then(r=>r.json())
.then(data=>showToast(data.error?'❌ '+data.error.substring(0,100):'✅ 已提交批量重抓,可在任务页查看'))
.catch(()=>showToast('❌ 请求失败'));
}
}
function doConfirmAction() {