feat: add admin dashboard, pipeline service, lightbox, and update dependencies

This commit is contained in:
2026-06-09 09:32:10 +08:00
parent 0d293422ac
commit 32978b3fc5
50 changed files with 4054 additions and 1618 deletions
+49 -60
View File
@@ -2,18 +2,20 @@
from __future__ import annotations
import json
import logging
import re
from datetime import date, timedelta
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import RedirectResponse
from sqlalchemy import select
from sqlalchemy.orm import Session, joinedload
from app.config import settings
from app.database import get_db
from app.models import Paper
from app.utils import templates, today_str
from app.models import PAPER_FULL_LOAD, Paper
from app.utils import PAPERS_DIR, safe_json_loads, templates, today_str, latest_paper_date
logger = logging.getLogger(__name__)
@@ -21,9 +23,9 @@ router = APIRouter()
@router.get("/")
def index(request: Request):
"""重定向到 /day/{today}"""
return RedirectResponse(url=f"/day/{today_str()}")
def index(request: Request, db: Session = Depends(get_db)):
"""重定向到最新有论文的日期页"""
return RedirectResponse(url=f"/day/{latest_paper_date(db)}")
@router.get("/day/{date_str}")
@@ -39,23 +41,24 @@ def day_page(date_str: str, request: Request, db: Session = Depends(get_db)):
today = today_str()
papers = (
db.query(Paper)
.filter(Paper.paper_date == date_str)
.options(
joinedload(Paper.authors),
joinedload(Paper.tags),
joinedload(Paper.summary_status),
joinedload(Paper.bookmark),
db.execute(
select(Paper)
.where(Paper.paper_date == date_str)
.options(*PAPER_FULL_LOAD)
.order_by(Paper.upvotes.desc())
)
.order_by(Paper.upvotes.desc())
.scalars()
.unique()
.all()
)
dates_raw = (
db.query(Paper.paper_date)
.distinct()
.order_by(Paper.paper_date.desc())
.limit(30)
db.execute(
select(Paper.paper_date)
.distinct()
.order_by(Paper.paper_date.desc())
.limit(30)
)
.all()
)
available_dates = [
@@ -81,18 +84,17 @@ def day_page(date_str: str, request: Request, db: Session = Depends(get_db)):
def paper_detail(arxiv_id: str, request: Request, db: Session = Depends(get_db)):
"""论文详情页。"""
paper = (
db.query(Paper)
.filter(Paper.arxiv_id == arxiv_id)
.options(
joinedload(Paper.authors),
joinedload(Paper.tags),
joinedload(Paper.summary),
joinedload(Paper.summary_status),
joinedload(Paper.bookmark),
joinedload(Paper.reading_status),
joinedload(Paper.note),
db.execute(
select(Paper)
.where(Paper.arxiv_id == arxiv_id)
.options(
joinedload(Paper.summary),
joinedload(Paper.note),
*PAPER_FULL_LOAD,
)
)
.first()
.unique()
.scalar_one_or_none()
)
if not paper:
raise HTTPException(status_code=404, detail="Paper not found")
@@ -108,28 +110,15 @@ def paper_detail(arxiv_id: str, request: Request, db: Session = Depends(get_db))
images = _get_paper_images(arxiv_id)
# 预处理 JSON 字段供模板直接使用
import json as _json
prereqs = {}
if paper.summary and paper.summary.prerequisites_json:
try:
prereqs = _json.loads(paper.summary.prerequisites_json)
except (ValueError, TypeError):
pass
benchmarks = []
if paper.summary and paper.summary.results_benchmarks_json:
try:
benchmarks = _json.loads(paper.summary.results_benchmarks_json)
except (ValueError, TypeError):
pass
figures_raw = []
if paper.summary and paper.summary.figures_json:
try:
figures_raw = _json.loads(paper.summary.figures_json)
except (ValueError, TypeError):
pass
prereqs = safe_json_loads(
paper.summary.prerequisites_json if paper.summary else None, default={}
)
benchmarks = safe_json_loads(
paper.summary.results_benchmarks_json if paper.summary else None, default=[]
)
figures_raw = safe_json_loads(
paper.summary.figures_json if paper.summary else None, default=[]
)
linked_figures = _link_figures_with_images(figures_raw, images, arxiv_id)
@@ -228,9 +217,12 @@ def _get_similar_papers(db: Session, arxiv_id: str, top_k: int = 6) -> list[dict
return []
papers = (
db.query(Paper)
.filter(Paper.arxiv_id.in_(list(papers_info.keys())))
.options(joinedload(Paper.tags))
db.execute(
select(Paper)
.where(Paper.arxiv_id.in_(list(papers_info.keys())))
.options(joinedload(Paper.tags))
)
.scalars()
.all()
)
@@ -260,7 +252,7 @@ def _get_similar_papers(db: Session, arxiv_id: str, top_k: int = 6) -> list[dict
def _get_paper_images(arxiv_id: str) -> list[dict]:
"""获取论文提取的图片列表。"""
images_dir = Path("data/papers") / arxiv_id / "images"
images_dir = PAPERS_DIR / arxiv_id / "images"
if not images_dir.exists():
return []
@@ -286,15 +278,12 @@ def _link_figures_with_images(
if not figures or not images:
return figures
import json as _json
import re
manifest_path = Path("data/papers") / arxiv_id / "images" / "manifest.json"
manifest_path = PAPERS_DIR / arxiv_id / "images" / "manifest.json"
if not manifest_path.exists():
return figures
try:
manifest = _json.loads(manifest_path.read_text(encoding="utf-8"))
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (ValueError, TypeError):
return figures