import { useState, useEffect } from 'react' import { Table, Input, Modal, Skeleton, Button, Space, message } from 'antd' import { FileTextOutlined, SearchOutlined, CloseOutlined, DownloadOutlined } from '@ant-design/icons' import ReactMarkdown from 'react-markdown' const { Search } = Input export default function ReportsViewer() { const [loading, setLoading] = useState(true) const [reports, setReports] = useState([]) const [selectedReport, setSelectedReport] = useState(null) const [reportContent, setReportContent] = useState(null) const [loadingContent, setLoadingContent] = useState(false) const [searchText, setSearchText] = useState('') useEffect(() => { fetchReports() }, []) const fetchReports = async () => { setLoading(true) try { const res = await fetch('/api/reports/list') if (!res.ok) throw new Error(`请求失败: ${res.status}`) const data = await res.json() setReports(data) } catch { setReports([]) } finally { setLoading(false) } } const handleExportCsv = async () => { try { const res = await fetch('/api/reports/export') if (!res.ok) throw new Error('导出失败') const blob = await res.blob() const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url; a.download = 'tradingagents_reports.csv'; a.click() URL.revokeObjectURL(url) } catch (e) { message.error(e.message) } } const handleExportPdf = async (ticker, date) => { try { const res = await fetch(`/api/reports/${ticker}/${date}/pdf`) if (!res.ok) throw new Error('导出失败') const blob = await res.blob() const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url; a.download = `${ticker}_${date}_report.pdf`; a.click() URL.revokeObjectURL(url) } catch (e) { message.error(e.message) } } const handleViewReport = async (record) => { setSelectedReport(record) setLoadingContent(true) try { const res = await fetch(`/api/reports/${record.ticker}/${record.date}`) if (!res.ok) throw new Error(`加载失败: ${res.status}`) const data = await res.json() setReportContent(data) } catch (err) { setReportContent({ report: `# 加载失败\n\n无法加载报告: ${err.message}` }) } finally { setLoadingContent(false) } } const filteredReports = reports.filter( (r) => r.ticker.toLowerCase().includes(searchText.toLowerCase()) || r.date.includes(searchText) ) const columns = [ { title: '代码', dataIndex: 'ticker', key: 'ticker', width: 120, render: (text) => ( {text} ), }, { title: '日期', dataIndex: 'date', key: 'date', width: 120, render: (text) => ( {text} ), }, { title: '操作', key: 'action', width: 100, render: (_, record) => ( ), }, ] return (
{/* Search + Export */}
setSearchText(e.target.value)} prefix={} size="large" style={{ flex: 1 }} />
{/* Reports Table */}
{loading ? (
) : filteredReports.length === 0 ? (
暂无历史报告
在股票筛选页面提交分析任务后,报告将显示在这里
) : ( `${r.ticker}-${r.date}`} pagination={{ pageSize: 10 }} size="middle" /> )} {/* Report Modal */} {selectedReport.ticker} {selectedReport.date} ) : null } open={!!selectedReport} onCancel={() => { setSelectedReport(null) setReportContent(null) }} footer={ selectedReport ? ( ) : null } width={800} closeIcon={} styles={{ wrapper: { maxWidth: '95vw' }, body: { maxHeight: '70vh', overflow: 'auto', padding: 'var(--space-6)' }, header: { padding: 'var(--space-4) var(--space-6)', borderBottom: '1px solid rgba(0,0,0,0.08)' }, }} > {loadingContent ? (
) : reportContent ? (
{reportContent.report || 'No content'}
) : null}
) }