"use client"; import { useEffect, useState, useCallback } from "react"; import { fetchAPI, postAPI } from "@/lib/api"; import type { LatestResult, SectorData, IndexOverview, DailyReviewResponse } from "@/lib/api"; import MarketTemp from "@/components/market-temp"; import StockCard from "@/components/stock-card"; import SectorHeatmap from "@/components/sector-heatmap"; import { useWebSocket } from "@/hooks/use-websocket"; import { useAuth } from "@/hooks/use-auth"; import { ThemeToggle } from "@/components/theme-toggle"; import { markdownToHtml } from "@/lib/markdown"; interface ScanStatus { is_trading: boolean; scan_mode: string; description: string; } export default function DashboardPage() { const { user } = useAuth(); const [data, setData] = useState(null); const [sectors, setSectors] = useState([]); const [scanStatus, setScanStatus] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [refreshResult, setRefreshResult] = useState(null); const [llmEnabled, setLlmEnabled] = useState(false); const [indices, setIndices] = useState([]); const [dailyReview, setDailyReview] = useState(null); const [generatingReview, setGeneratingReview] = useState(false); const loadData = useCallback(async () => { try { const [latest, sectorData, status, health, overview, reviewData] = await Promise.all([ fetchAPI("/api/recommendations/latest"), fetchAPI("/api/sectors/hot?limit=8"), fetchAPI("/api/recommendations/status"), fetchAPI<{ llm_enabled: boolean }>("/api/health"), fetchAPI("/api/market/overview").catch(() => []), fetchAPI("/api/market/daily-review").catch(() => ({ reviews: [] })), ]); setData(latest); setSectors(sectorData); setScanStatus(status); setLlmEnabled(health.llm_enabled); setIndices(overview); if (reviewData.reviews?.length > 0) { setDailyReview(reviewData.reviews[0].content); } } catch (e) { console.error("加载数据失败:", e); } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); useWebSocket( useCallback(() => { loadData(); }, [loadData]), ["llm_analysis_ready", "sector_scan_ready", "scan_complete"] ); const handleRefresh = async () => { setRefreshing(true); setRefreshResult(null); try { const res = await postAPI<{ status: string; count: number; temperature: number; scan_mode: string; is_trading: boolean; }>("/api/recommendations/refresh?scan_session=manual"); const modeLabel = res.scan_mode === "intraday" ? "盘中实时" : "盘后"; setRefreshResult(`${modeLabel}扫描完成,发现 ${res.count} 只股票`); await loadData(); } catch (e) { console.error("刷新失败:", e); setRefreshResult("扫描失败,请重试"); } finally { setRefreshing(false); setTimeout(() => setRefreshResult(null), 5000); } }; if (loading) { return (
); } return (
{/* Header bar */}

Dragon AI Agent

{scanStatus && (

{scanStatus.is_trading ? ( 交易中 · 实时行情 ) : ( 已收盘 · Tushare 日级数据 )}

)}
{user?.role === "admin" && ( )}
{/* Scan result toast */} {refreshResult && (
{refreshResult}
)} {/* Market temp + Sector heatmap */}
{/* Daily Review */}

每日复盘

{llmEnabled && ( )}
{dailyReview ? (
) : (
暂无复盘报告
{llmEnabled ? "点击「生成复盘」AI自动分析" : "配置LLM后自动生成"}
)}
{/* Recommendations */}

今日推荐 {data?.recommendations?.length ? ( {data.recommendations.length} ) : ""}

查看全部
{!data?.recommendations?.length ? (
暂无推荐
{user?.role === "admin" ? `点击 ${scanStatus?.is_trading ? "「盘中扫描」" : "「立即扫描」"} 开始分析` : "等待系统自动扫描更新"}
) : (
{data.recommendations.slice(0, 6).map((rec) => ( ))}
)}
); }