- AI分析页改为直接展示最新报告,历史存数据库 - 新增浅色/深色/系统自动主题切换 - 市场看板、AI分析页适配主题变量 - 移动端表格可滑动、按钮自动换行 - Mermaid图表支持 - Markdown表格渲染支持(remark-gfm)
142 lines
5.6 KiB
TypeScript
142 lines
5.6 KiB
TypeScript
import * as React from "react";
|
|
import { Link, createFileRoute } from "@tanstack/react-router";
|
|
import { ArrowLeft, Clock, Loader2, AlertCircle, RefreshCw, Wrench, Zap, Calendar } from "lucide-react";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import Markdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import { Mermaid } from "../components/Mermaid";
|
|
import { fetchAiLatestReport, triggerAiAnalysis, fetchAiReport } from "../lib/ai-analysis-api";
|
|
import { Button } from "../components/ui/button";
|
|
import { Card, CardContent } from "../components/ui/card";
|
|
import { toast } from "sonner";
|
|
|
|
export const Route = createFileRoute("/ai-analysis")({
|
|
component: AiAnalysisPage,
|
|
});
|
|
|
|
function AiAnalysisPage() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const { data: report, isLoading, isError, refetch } = useQuery({
|
|
queryKey: ["ai-report-latest"],
|
|
queryFn: fetchAiLatestReport,
|
|
retry: false,
|
|
});
|
|
|
|
const triggerMutation = useMutation({
|
|
mutationFn: triggerAiAnalysis,
|
|
onSuccess: () => {
|
|
toast.success("AI 分析已开始,请稍候...");
|
|
queryClient.invalidateQueries({ queryKey: ["ai-report-latest"] });
|
|
},
|
|
onError: (err: Error) => {
|
|
toast.error(err.message || "触发失败");
|
|
},
|
|
});
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background text-foreground">
|
|
<div className="max-w-4xl mx-auto px-3 sm:px-4 py-4 sm:py-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-4 sm:mb-6">
|
|
<div className="flex items-center gap-2 sm:gap-3">
|
|
<Link to="/" className="text-muted-foreground hover:text-foreground transition-colors">
|
|
<ArrowLeft className="h-5 w-5" />
|
|
</Link>
|
|
<h1 className="text-lg sm:text-xl md:text-2xl font-bold flex items-center gap-2">
|
|
<Calendar className="h-5 w-5 text-primary" />
|
|
AI 收盘分析
|
|
</h1>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => triggerMutation.mutate()}
|
|
disabled={triggerMutation.isPending}
|
|
className="gap-1.5 text-xs sm:text-sm"
|
|
>
|
|
{triggerMutation.isPending ? (
|
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
) : (
|
|
<Zap className="h-3.5 w-3.5" />
|
|
)}
|
|
<span className="hidden sm:inline">立即分析</span>
|
|
<span className="sm:hidden">分析</span>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
{isLoading ? (
|
|
<div className="flex flex-col items-center gap-3 py-20">
|
|
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
|
<p className="text-sm text-muted-foreground">加载中...</p>
|
|
</div>
|
|
) : isError || !report ? (
|
|
<div className="flex flex-col items-center gap-3 py-20">
|
|
<AlertCircle className="h-6 w-6 text-destructive" />
|
|
<p className="text-sm text-muted-foreground">暂无分析报告</p>
|
|
<p className="text-xs text-muted-foreground/60">点击"立即分析"生成今日报告</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{/* Meta Info */}
|
|
<Card className="bg-card border-border mb-3 sm:mb-4">
|
|
<CardContent className="p-3 sm:p-4">
|
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 sm:gap-4 text-xs sm:text-sm text-muted-foreground">
|
|
<span className="flex items-center gap-1.5">
|
|
<Clock className="h-3.5 w-3.5 shrink-0" />
|
|
<span className="truncate">{report.created_at}</span>
|
|
</span>
|
|
<span className="truncate">{report.model}</span>
|
|
<span>{report.tokens_used?.toLocaleString()} tokens</span>
|
|
{report.toolsUsed && report.toolsUsed.length > 0 && (
|
|
<span className="flex items-center gap-1.5">
|
|
<Wrench className="h-3.5 w-3.5 shrink-0" />
|
|
{report.toolsUsed.length} 个工具
|
|
</span>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Report Content */}
|
|
<Card className="bg-card border-border">
|
|
<CardContent className="p-3 sm:p-4 md:p-6 ai-report-content">
|
|
<Markdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={{
|
|
table({ children, ...props }) {
|
|
return (
|
|
<div className="overflow-x-auto -mx-3 sm:mx-0 px-3 sm:px-0">
|
|
<table {...props}>{children}</table>
|
|
</div>
|
|
);
|
|
},
|
|
code({ className, children, ...props }) {
|
|
const match = /language-(\w+)/.exec(className || "");
|
|
if (match && match[1] === "mermaid") {
|
|
return <Mermaid chart={String(children).replace(/\n$/, "")} />;
|
|
}
|
|
return (
|
|
<code className={className} {...props}>
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
}}
|
|
>
|
|
{report.content}
|
|
</Markdown>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Disclaimer */}
|
|
<p className="text-xs text-muted-foreground/60 text-center mt-4 sm:mt-6">
|
|
本报告由 AI 生成,仅供参考,不构成投资建议
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|