diff --git a/backend/dist/admin.html b/backend/dist/admin.html index 3ffcc9c..e869f52 100644 --- a/backend/dist/admin.html +++ b/backend/dist/admin.html @@ -334,7 +334,7 @@ function renderReports(list) { document.getElementById('recentBody').innerHTML = list.map(r => ` ${r.trade_date} - ${r.title || '-'} + ${r.title || '-'} ${r.tokens_used || 0} ` ).join('') || '暂无数据'; @@ -349,7 +349,7 @@ function renderAllReports(list) { ${r.tokens_used || 0} ${r.created_at || '-'} - 查看 + 查看 ` diff --git a/backend/routes/ai_analysis.py b/backend/routes/ai_analysis.py index 5ebe5ce..c69f03d 100644 --- a/backend/routes/ai_analysis.py +++ b/backend/routes/ai_analysis.py @@ -77,6 +77,29 @@ async def list_reports(): conn.close() +@router.get("/ai-analysis/by-date/{trade_date}", summary="按交易日获取 AI 分析报告") +async def get_report_by_date(trade_date: str): + try: + datetime.strptime(trade_date, "%Y-%m-%d") + except ValueError: + raise HTTPException(status_code=400, detail="日期格式错误,应为 YYYY-MM-DD") + conn = get_connection() + try: + row = conn.execute( + """SELECT r.*, + (SELECT COUNT(*) FROM ai_reports WHERE trade_date <= r.trade_date) AS issue_number + FROM ai_reports r WHERE r.trade_date = ? AND r.report_type = 'daily' LIMIT 1""", + (trade_date,) + ).fetchone() + if not row: + raise HTTPException(status_code=404, detail=f"{trade_date} 暂无分析报告") + item = dict_from_row(row) + item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]") + return JSONResponse({"data": item}, headers=_NO_CACHE_HEADERS) + finally: + conn.close() + + @router.get("/ai-analysis/{report_id}", summary="AI 分析报告详情") async def get_report(report_id: int): conn = get_connection() diff --git a/src/lib/ai-analysis-api.ts b/src/lib/ai-analysis-api.ts index 8ea6a6c..72d2e4c 100644 --- a/src/lib/ai-analysis-api.ts +++ b/src/lib/ai-analysis-api.ts @@ -35,6 +35,13 @@ export async function fetchAiReports(): Promise { return result.data || []; } +export async function fetchAiReportByDate(tradeDate: string): Promise { + const resp = await fetch(`${API_BASE}/api/ai-analysis/by-date/${tradeDate}`); + if (!resp.ok) throw new Error(`请求失败 (${resp.status})`); + const result = await resp.json(); + return result.data; +} + export async function fetchAiReport(id: number): Promise { const resp = await fetch(`${API_BASE}/api/ai-analysis/${id}`); if (!resp.ok) throw new Error(`请求失败 (${resp.status})`); diff --git a/src/routes/ai-analysis.tsx b/src/routes/ai-analysis.tsx index 4b44bb8..18b1bcc 100644 --- a/src/routes/ai-analysis.tsx +++ b/src/routes/ai-analysis.tsx @@ -1,15 +1,22 @@ import * as React from "react"; -import { Link, createFileRoute } from "@tanstack/react-router"; +import { Link, createFileRoute, useNavigate } from "@tanstack/react-router"; import { ArrowLeft, Clock, Loader2, AlertCircle, Wrench, Calendar } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { Components } from "react-markdown"; import { Mermaid } from "../components/Mermaid"; -import { fetchAiLatestReport } from "../lib/ai-analysis-api"; +import { fetchAiLatestReport, fetchAiReportByDate, fetchAiReports } from "../lib/ai-analysis-api"; import { Card, CardContent } from "../components/ui/card"; export const Route = createFileRoute("/ai-analysis")({ + validateSearch: (search: Record) => { + return { + date: typeof search.date === "string" ? search.date : undefined, + // 兼容 ?data=YYYY-MM-DD 写法 + data: typeof search.data === "string" ? search.data : undefined, + }; + }, component: AiAnalysisPage, }); @@ -62,12 +69,29 @@ function splitReport(content: string): { intro: string; sections: { title: strin } function AiAnalysisPage() { + const navigate = useNavigate(); + const { date, data } = Route.useSearch(); + const tradeDate = date ?? data ?? undefined; + const isHistorical = !!tradeDate; + const { data: report, isLoading, isError } = useQuery({ - queryKey: ["ai-report-latest"], - queryFn: fetchAiLatestReport, + queryKey: isHistorical ? ["ai-report", tradeDate] : ["ai-report-latest"], + queryFn: () => (isHistorical ? fetchAiReportByDate(tradeDate!) : fetchAiLatestReport()), retry: false, }); + const { data: history = [] } = useQuery({ + queryKey: ["ai-reports"], + queryFn: fetchAiReports, + retry: false, + }); + + const goDate = (d?: string) => { + navigate({ to: "/ai-analysis", search: d ? { date: d } : {} }); + }; + + const selectedDate = tradeDate ?? report?.trade_date ?? ""; + const mdComponents: Components = { table({ children, ...props }) { return ( @@ -96,17 +120,34 @@ function AiAnalysisPage() {
{/* Header */} -
- - - -

- - AI 收盘分析 - {report?.issue_number ? ( - 总第 {report.issue_number} 期 - ) : null} -

+
+
+ + + +

+ + AI 收盘分析 + {report?.issue_number ? ( + 总第 {report.issue_number} 期 + ) : null} +

+
+ {history.length > 0 && ( + + )}
{/* Content */} @@ -118,8 +159,20 @@ function AiAnalysisPage() { ) : isError || !report ? (
-

暂无分析报告

-

请通过管理面板触发 AI 分析

+

+ {isHistorical ? `${tradeDate} 暂无分析报告` : "暂无分析报告"} +

+ {isHistorical ? ( + + 查看最新报告 + + ) : ( +

请通过管理面板触发 AI 分析

+ )}
) : ( <>