TradingAgents/frontend/app/analysis/results/page.tsx

226 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { useAnalysisContext } from "@/context/AnalysisContext";
import { PriceChart } from "@/components/analysis/PriceChart";
import { DownloadReports } from "@/components/analysis/DownloadReports";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { ChevronLeft } from "lucide-react";
const ANALYSTS = [
// === 分析師團隊 ===
{
key: "market",
label: "市場分析師",
reportKey: "market_report",
description: "技術分析與市場趨勢評估"
},
{
key: "social",
label: "社群媒體分析師",
reportKey: "sentiment_report",
description: "社群情緒與市場氛圍分析"
},
{
key: "news",
label: "新聞分析師",
reportKey: "news_report",
description: "新聞事件與影響分析"
},
{
key: "fundamentals",
label: "基本面分析師",
reportKey: "fundamentals_report",
description: "財務數據與基本面分析"
},
// === 研究團隊 ===
{
key: "bull",
label: "看漲研究員",
reportKey: "investment_debate_state.bull_history",
description: "看漲觀點與投資論據"
},
{
key: "bear",
label: "看跌研究員",
reportKey: "investment_debate_state.bear_history",
description: "看跌觀點與風險警告"
},
{
key: "research_manager",
label: "研究經理",
reportKey: "investment_debate_state.judge_decision",
description: "研究團隊綜合決策"
},
// === 交易員 ===
{
key: "trader",
label: "交易員",
reportKey: "trader_investment_plan",
description: "交易執行計劃與策略"
},
// === 風險管理團隊 ===
{
key: "risky",
label: "激進分析師",
reportKey: "risk_debate_state.risky_history",
description: "高風險高回報策略分析"
},
{
key: "safe",
label: "保守分析師",
reportKey: "risk_debate_state.safe_history",
description: "穩健保守策略分析"
},
{
key: "neutral",
label: "中立分析師",
reportKey: "risk_debate_state.neutral_history",
description: "中立平衡策略分析"
},
{
key: "risk_manager",
label: "風險經理",
reportKey: "risk_debate_state.judge_decision",
description: "風險管理綜合決策"
},
];
// 獲取嵌套對象的值
const getNestedValue = (obj: any, path: string) => {
return path.split('.').reduce((current, key) => current?.[key], obj);
};
export default function AnalysisResultsPage() {
const router = useRouter();
const { analysisResult, taskId } = useAnalysisContext();
const [selectedAnalyst, setSelectedAnalyst] = useState("market");
// 如果沒有結果,重定向到分析頁面
useEffect(() => {
if (!analysisResult) {
router.push("/analysis");
}
}, [analysisResult, router]);
if (!analysisResult) {
return (
<div className="container mx-auto px-4 py-12">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4"></h1>
<p className="text-gray-600 mb-4"></p>
<Button onClick={() => router.push("/analysis")}>
</Button>
</div>
</div>
);
}
const currentAnalyst = ANALYSTS.find(a => a.key === selectedAnalyst);
const currentReport = getNestedValue(analysisResult.reports, currentAnalyst?.reportKey || "");
return (
<div className="container mx-auto px-4 py-12">
<div className="max-w-7xl mx-auto space-y-8">
{/* Header */}
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<h1 className="text-4xl font-bold mb-2">
{analysisResult.ticker}
</h1>
<p className="text-gray-600 dark:text-gray-400">
{analysisResult.analysis_date}
</p>
</div>
<Button
variant="outline"
onClick={() => router.push("/analysis")}
className="gap-2"
>
<ChevronLeft className="h-4 w-4" />
</Button>
</div>
{/* 分析師選擇 Tabs */}
<Tabs value={selectedAnalyst} onValueChange={setSelectedAnalyst} className="w-full">
<TabsList className="grid w-full grid-cols-2 md:grid-cols-3 lg:grid-cols-4 h-auto gap-2">
{ANALYSTS.map(analyst => (
<TabsTrigger
key={analyst.key}
value={analyst.key}
className="text-sm md:text-base py-2"
>
{analyst.label}
</TabsTrigger>
))}
</TabsList>
{ANALYSTS.map(analyst => (
<TabsContent key={analyst.key} value={analyst.key} className="mt-6">
<div className="space-y-6">
{/* 價格圖表 - 每個分析師都有 */}
{analysisResult.price_data && analysisResult.price_stats && (
<PriceChart
priceData={analysisResult.price_data}
priceStats={analysisResult.price_stats}
ticker={analysisResult.ticker}
/>
)}
{/* 分析師報告 */}
<Card>
<CardHeader>
<CardTitle>{analyst.label} </CardTitle>
<CardDescription>
{analyst.description}
</CardDescription>
</CardHeader>
<CardContent>
{currentReport ? (
<div className="prose prose-sm max-w-none dark:prose-invert">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{currentReport}
</ReactMarkdown>
</div>
) : (
<div className="text-center py-8">
<p className="text-gray-500 dark:text-gray-400">
</p>
<p className="text-sm text-gray-400 dark:text-gray-500 mt-2">
</p>
</div>
)}
</CardContent>
</Card>
</div>
</TabsContent>
))}
</Tabs>
{/* Download Reports Section - 放在分析報告下方 */}
{taskId && analysisResult.reports && (
<DownloadReports
ticker={analysisResult.ticker}
analysisDate={analysisResult.analysis_date}
taskId={taskId}
analysts={ANALYSTS}
reports={analysisResult.reports}
/>
)}
</div>
</div>
);
}