Files
signalscout-backend/app/api/routes.py
T

102 lines
3.8 KiB
Python

from __future__ import annotations
from typing import Optional
from fastapi import APIRouter, Depends
from fastapi.responses import HTMLResponse
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.config import Settings, get_settings
from app.db.models import AgentRun, Report, Signal, Topic
from app.db.session import get_db
from app.schemas.agent import (
DashboardRead,
ReportRead,
RunRead,
SignalRead,
TopicCreate,
TopicRead,
)
from app.ui.home import HOME_PAGE_HTML
router = APIRouter()
@router.get("/", response_class=HTMLResponse)
def home() -> HTMLResponse:
# 根路径返回业务工作台,避免用户直接打开域名时看到接口404。
return HTMLResponse(HOME_PAGE_HTML)
@router.get("/health")
def health(settings: Settings = Depends(get_settings)) -> dict:
# 健康检查只返回服务身份,避免暴露密钥和内部地址。
return {"status": "ok", "app": settings.app_name, "env": settings.env}
@router.get("/agent/runs", response_model=list[RunRead])
def list_runs(db: Session = Depends(get_db)) -> list[AgentRun]:
# 运行列表保持紧凑,方便仪表盘和命令行查看最近状态。
return list(db.scalars(select(AgentRun).order_by(AgentRun.id.desc()).limit(20)).all())
@router.get("/signals", response_model=list[SignalRead])
def list_signals(limit: int = 50, db: Session = Depends(get_db)) -> list[Signal]:
# 情报列表按采集时间倒序返回,是仪表盘的主要阅读入口。
safe_limit = max(1, min(limit, 200))
return list(
db.scalars(_visible_signals_query().order_by(Signal.id.desc()).limit(safe_limit)).all()
)
@router.get("/reports/latest", response_model=Optional[ReportRead])
def latest_report(db: Session = Depends(get_db)) -> Optional[Report]:
# 最新日报提供一份可直接阅读的每日简报。
return db.scalar(select(Report).order_by(Report.id.desc()).limit(1))
@router.get("/dashboard", response_model=DashboardRead)
def dashboard(db: Session = Depends(get_db)) -> DashboardRead:
# 单个仪表盘接口返回运行、信号、日报和主题,前端不需要理解内部调度细节。
runs = list(db.scalars(select(AgentRun).order_by(AgentRun.id.desc()).limit(10)).all())
signals = list(db.scalars(_visible_signals_query().order_by(Signal.id.desc()).limit(80)).all())
report = db.scalar(select(Report).order_by(Report.id.desc()).limit(1))
topics = list(db.scalars(select(Topic).order_by(Topic.id.asc())).all())
return DashboardRead(
runs=runs,
signals=signals,
latest_report=report,
topics=topics,
)
@router.get("/logs/recent", response_model=list[str])
def recent_logs(settings: Settings = Depends(get_settings)) -> list[str]:
# 最近日志用于定位Agent和模型供应商调用问题。
if not settings.log_file.exists():
return []
lines = settings.log_file.read_text(encoding="utf-8", errors="replace").splitlines()
return lines[-settings.log_tail_lines :]
@router.post("/topics", response_model=TopicRead)
def create_topic(payload: TopicCreate, db: Session = Depends(get_db)) -> Topic:
# 主题让Agent可以在不改代码的情况下扩展关注范围。
topic = Topic(name=payload.name, description=payload.description, enabled=payload.enabled)
db.add(topic)
db.commit()
db.refresh(topic)
return topic
@router.get("/topics", response_model=list[TopicRead])
def list_topics(db: Session = Depends(get_db)) -> list[Topic]:
# 主题列表让操作者确认当前情报目标。
return list(db.scalars(select(Topic).order_by(Topic.id.asc())).all())
def _visible_signals_query():
# 运行中的信号和完成后的信号走同一张表,查询函数保留统一排序入口。
return select(Signal).join(AgentRun, Signal.run_id == AgentRun.id)