From 6c7d8fe4bcb780dc3e24d9a1c5300a8897421565 Mon Sep 17 00:00:00 2001
From: Sakurasan <26715255+Sakurasan@users.noreply.github.com>
Date: Sat, 12 Sep 2026 20:21:44 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20AI=E5=88=86=E6=9E=90=E9=A1=B5=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E6=8C=89=E6=97=A5=E6=9C=9F=E6=9F=A5=E7=9C=8B=E5=8E=86?=
=?UTF-8?q?=E5=8F=B2=E6=8A=A5=E5=91=8A=EF=BC=88=3Fdate=3D=EF=BC=8C?=
=?UTF-8?q?=E5=85=BC=E5=AE=B9=3Fdata=3D=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/dist/admin.html | 4 +-
backend/routes/ai_analysis.py | 23 +++++++++
src/lib/ai-analysis-api.ts | 7 +++
src/routes/ai-analysis.tsx | 87 ++++++++++++++++++++++++++++-------
4 files changed, 102 insertions(+), 19 deletions(-)
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 分析
+ )}
) : (
<>