Skip to main content
Glama
KennanYang

Financial Report Generator MCP Server

by KennanYang
ai-enhanced-report-generator.tsx9.71 kB
'use client'; import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Alert, AlertDescription } from '../ui/alert'; import { useFinancialStore } from '../../store/financial-store'; import { Brain, Sparkles, Loader2, AlertCircle, Download, RefreshCw, CheckCircle } from 'lucide-react'; import { logError } from '../../lib/error-monitor'; export function AIEnhancedReportGenerator() { const [isGenerating, setIsGenerating] = useState(false); const [aiResult, setAiResult] = useState<string>(''); const [error, setError] = useState<string>(''); const [lastSuccessTime, setLastSuccessTime] = useState<Date | null>(null); const [retryCount, setRetryCount] = useState(0); const [isRetrying, setIsRetrying] = useState(false); const { selectedCompany, generateReport } = useFinancialStore(); const MAX_RETRIES = 3; const TIMEOUT_DURATION = 30000; // 30秒超时 // 清除之前的AI结果 useEffect(() => { if (selectedCompany) { setAiResult(''); setError(''); setRetryCount(0); } }, [selectedCompany]); // 重置错误状态 const resetError = useCallback(() => { setError(''); setRetryCount(0); }, []); // 生成AI报告的主要函数 const generateAIReport = useCallback(async (isRetry = false) => { if (!selectedCompany) { setError('请先选择一个公司'); return; } if (isRetry) { setIsRetrying(true); } else { setIsGenerating(true); } setError(''); if (!isRetry) { setAiResult(''); } try { // 创建超时控制器 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_DURATION); // 调用AI分析API const response = await fetch('/api/ai-analysis', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ company: selectedCompany.symbol, analysisType: 'comprehensive' }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`服务器响应错误: ${response.status} ${response.statusText}`); } const data = await response.json(); if (data.error) { throw new Error(data.error); } setAiResult(data.analysis || 'AI分析完成,但未返回结果'); setLastSuccessTime(new Date()); setRetryCount(0); // 成功后的状态更新 if (isRetry) { setIsRetrying(false); } else { setIsGenerating(false); } } catch (err) { console.error('AI分析失败:', err); // 记录错误到监控系统 logError('AIEnhancedReportGenerator', err, { company: selectedCompany?.symbol, analysisType: 'comprehensive', isRetry, retryCount }); let errorMessage = 'AI分析失败,请稍后重试'; if (err instanceof Error) { if (err.name === 'AbortError') { errorMessage = '请求超时,请检查网络连接或稍后重试'; } else if (err.message.includes('Failed to fetch')) { errorMessage = '网络连接失败,请检查网络设置'; } else if (err.message.includes('服务器响应错误')) { errorMessage = err.message; } else { errorMessage = err.message; } } setError(errorMessage); // 如果不是重试,增加重试计数 if (!isRetry && retryCount < MAX_RETRIES) { setRetryCount(prev => prev + 1); } if (isRetry) { setIsRetrying(false); } else { setIsGenerating(false); } } }, [selectedCompany, retryCount]); // 重试函数 const handleRetry = useCallback(() => { if (retryCount < MAX_RETRIES) { generateAIReport(true); } }, [retryCount, generateAIReport]); // 下载Markdown报告 const downloadMarkdownReport = () => { if (!aiResult || !selectedCompany) return; const timestamp = new Date().toLocaleString('zh-CN'); const markdownContent = `# ${selectedCompany.name} (${selectedCompany.symbol}) AI分析报告 **生成时间**: ${timestamp} **分析类型**: 全面AI分析 **AI模型**: KainanAI-Financial-Analysis-v1.0 --- ${aiResult} --- *本报告由AI系统自动生成,仅供参考,不构成投资建议。投资有风险,入市需谨慎。*`; const blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${selectedCompany.symbol}_AI分析报告_${new Date().toISOString().split('T')[0]}.md`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; // 检查是否可以重试 const canRetry = retryCount < MAX_RETRIES && !isGenerating && !isRetrying; const showRetryButton = error && canRetry; return ( <Card> <CardHeader> <CardTitle className="flex items-center gap-2"> <Brain className="h-5 w-5 text-purple-500" /> AI增强分析 <Badge variant="secondary" className="ml-auto"> <Sparkles className="h-3 w-3 mr-1" /> AI驱动 </Badge> </CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="space-y-2"> <label className="text-sm font-medium">目标公司</label> <div className="flex items-center gap-2"> <Badge variant="outline">{selectedCompany?.symbol || 'N/A'}</Badge> <span className="text-sm text-muted-foreground"> {selectedCompany?.name || '未知公司'} </span> </div> </div> {/* 状态指示器 */} {lastSuccessTime && ( <div className="flex items-center gap-2 text-xs text-green-600"> <CheckCircle className="h-3 w-3" /> 上次成功: {lastSuccessTime.toLocaleString('zh-CN')} </div> )} <Button onClick={() => generateAIReport(false)} disabled={!selectedCompany || isGenerating || isRetrying} className="w-full" size="lg" > {isGenerating ? ( <> <Loader2 className="mr-2 h-5 w-5 animate-spin" /> AI分析中... </> ) : isRetrying ? ( <> <RefreshCw className="mr-2 h-5 w-5 animate-spin" /> 重试中... ({retryCount + 1}/{MAX_RETRIES}) </> ) : ( <> <Brain className="mr-2 h-5 w-5" /> 开始AI分析 </> )} </Button> {/* 错误提示 */} {error && ( <Alert variant="destructive"> <AlertCircle className="h-4 w-4" /> <AlertDescription className="space-y-2"> <div>{error}</div> {showRetryButton && ( <div className="flex items-center gap-2"> <span className="text-xs">重试次数: {retryCount}/{MAX_RETRIES}</span> <Button onClick={handleRetry} variant="outline" size="sm" className="h-6 px-2" > <RefreshCw className="h-3 w-3 mr-1" /> 重试 </Button> </div> )} {retryCount >= MAX_RETRIES && ( <div className="text-xs"> 已达到最大重试次数,请稍后重试或联系技术支持 </div> )} </AlertDescription> </Alert> )} {/* AI分析结果 */} {aiResult && ( <div className="space-y-3"> <div className="flex items-center justify-between"> <label className="text-sm font-medium">AI分析结果</label> <div className="flex gap-2"> <Button onClick={downloadMarkdownReport} variant="outline" size="sm" className="flex items-center gap-2" > <Download className="h-4 w-4" /> 下载MD报告 </Button> <Button onClick={() => { setAiResult(''); setError(''); setRetryCount(0); }} variant="outline" size="sm" className="flex items-center gap-2" > <RefreshCw className="h-4 w-4" /> 重新分析 </Button> </div> </div> <div className="p-3 bg-muted rounded-md text-sm"> <div className="whitespace-pre-wrap">{aiResult}</div> </div> </div> )} {/* 帮助信息 */} <div className="space-y-2 text-xs text-muted-foreground"> <div>* AI分析基于最新的财务数据和市场趋势,支持下载Markdown格式报告</div> <div>* 如果遇到问题,可以尝试重试或刷新页面</div> <div>* 网络不稳定时建议使用重试功能</div> </div> </CardContent> </Card> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KennanYang/financial-report'

If you have feedback or need assistance with the MCP directory API, please join our Discord server