import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useMutation } from '@tanstack/react-query'; import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { BarChart3, Search, Sparkles, Loader2, AlertCircle } from "lucide-react" import { useAnalysisTemplates, useLlmProviders } from "@/hooks/useConfig" import { client } from '@/api/client'; import { DataRequest } from '@/api/schema.gen'; import { z } from 'zod'; import { useToast } from "@/hooks/use-toast" type DataRequestDTO = z.infer; export function Dashboard() { const navigate = useNavigate(); const { toast } = useToast(); const [symbol, setSymbol] = useState(""); const [market, setMarket] = useState("CN"); const [templateId, setTemplateId] = useState(""); const { data: templates, isLoading: isTemplatesLoading } = useAnalysisTemplates(); const { data: llmProviders } = useLlmProviders(); const [validationError, setValidationError] = useState(null); // Auto-select first template when loaded useEffect(() => { if (templates && Object.keys(templates).length > 0 && !templateId) { setTemplateId(Object.keys(templates)[0]); } }, [templates, templateId]); // Validate template against providers useEffect(() => { if (!templateId || !templates || !templates[templateId] || !llmProviders) { setValidationError(null); return; } const selectedTemplate = templates[templateId]; const missingConfigs: string[] = []; Object.values(selectedTemplate.modules).forEach(module => { const modelId = module.llm_config?.model_id; if (modelId && llmProviders) { const modelExists = Object.values(llmProviders).some(provider => provider.models.some(m => m.model_id === modelId) ); if (!modelExists) { missingConfigs.push(`Module '${module.name}': Model '${modelId}' not found in any active provider`); } } }); if (missingConfigs.length > 0) { setValidationError(missingConfigs.join("; ")); } else { setValidationError(null); } }, [templateId, templates, llmProviders]); const startWorkflowMutation = useMutation({ mutationFn: async (payload: DataRequestDTO) => { return await client.start_workflow(payload); }, onSuccess: (data) => { navigate(`/report/${data.request_id}?symbol=${data.symbol}&market=${data.market}&templateId=${templateId}`); }, onError: (error) => { toast({ title: "Start Failed", description: "Failed to start analysis workflow. Please try again.", type: "error" }); console.error("Workflow start error:", error); } }); const handleStart = () => { if (!symbol || !templateId) return; startWorkflowMutation.mutate({ symbol, market, template_id: templateId }); }; return (

Fundamental Analysis

基于 AI Agent 的深度基本面投研平台

开始新的分析 输入股票代码,自动聚合多源数据并生成深度报告。
setSymbol(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && symbol && templateId) { handleStart(); } }} />
{validationError && (
Configuration Error: The selected template has invalid configurations.
{validationError.split('; ').map((err, i) => ( • {err} ))}
)}
) } function FeatureCard({ title, desc }: { title: string, desc: string }) { return (

{title}

{desc}

) }