// frontend/src/components/ChatPanel.tsx import React, { useState, useRef, useEffect } from 'react'; import { API_BASE } from '../config'; interface ChatMessage { id: string; role: 'user' | 'assistant' | 'system'; content: string; timestamp: Date; strategyConfig?: Record; executionPlan?: Array<{ step: number; tool: string; desc: string }>; riskReport?: RiskReport | null; } interface RiskReport { ticker: string; strategy: string; interval: string; risk_score: number; sections: Array<{ title: string; icon: string; insights: string[]; severity: string; }>; } interface ChatPanelProps { onRunBacktest: (config: Record) => void; isLoading: boolean; activeTicker: string; } const EXAMPLE_PROMPTS = [ "Backtest TSLA with dynamic routing strategy on daily bars", "Test NVDA with EMA crossover on 5-minute bars, ATR stop 1.5x", "Run mean reversion on SPY with RSI oversold at 10", "用日线回测 AAPL 的突破策略,ATR 止损 2.5 倍", "Compare Donchian breakout on AMD with 2x ATR trailing stop", ]; export const ChatPanel: React.FC = ({ onRunBacktest, isLoading, activeTicker }) => { const [messages, setMessages] = useState([ { id: 'welcome', role: 'system', content: `Welcome to Quant.ai Research Agent. Describe your trading research in natural language — I'll parse it into a strategy config, run the backtest, and generate a risk analysis report.\n\nTry: "Backtest TSLA with dynamic routing strategy on daily bars"`, timestamp: new Date() } ]); const [input, setInput] = useState(''); const [reportLoading, setReportLoading] = useState(false); const messagesEndRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const handleSend = async () => { const trimmed = input.trim(); if (!trimmed || isLoading) return; const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', content: trimmed, timestamp: new Date() }; setMessages(prev => [...prev, userMsg]); setInput(''); try { // Step 1: Parse prompt → strategy config const parseRes = await fetch(`${API_BASE}/api/agent/research`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: trimmed }) }); const parseData = await parseRes.json(); if (!parseData.success) { setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: `❌ Failed to parse: ${parseData.error}`, timestamp: new Date() }]); return; } const config = parseData.strategy_config; const plan = parseData.execution_plan; // Step 2: Show parsed config and plan const configMsg: ChatMessage = { id: (Date.now() + 1).toString(), role: 'assistant', content: `**${parseData.parsed_intent}**\n\nI've parsed your request into the following configuration. Click "Run Backtest" to execute, or modify the parameters in the settings panel.`, timestamp: new Date(), strategyConfig: config, executionPlan: plan }; setMessages(prev => [...prev, configMsg]); // Step 3: Auto-run backtest onRunBacktest(config); // Step 4: Generate risk report setReportLoading(true); try { const reportRes = await fetch(`${API_BASE}/api/report/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const reportData = await reportRes.json(); if (reportData.success && reportData.report) { const reportMsg: ChatMessage = { id: (Date.now() + 2).toString(), role: 'assistant', content: '', timestamp: new Date(), riskReport: reportData.report }; setMessages(prev => [...prev, reportMsg]); } } catch { // Risk report is optional — backtest still succeeded } finally { setReportLoading(false); } } catch (e) { setMessages(prev => [...prev, { id: Date.now().toString(), role: 'assistant', content: `❌ Connection failed: ${e}. Make sure the backend is running on ${API_BASE}`, timestamp: new Date() }]); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const renderRiskScore = (score: number) => { const color = score >= 70 ? 'var(--color-green)' : score >= 40 ? '#f5a623' : 'var(--color-red)'; const label = score >= 70 ? 'Low Risk' : score >= 40 ? 'Moderate Risk' : 'High Risk'; return (
{score}
{label}
); }; return (
🤖 AI Research Agent
{isLoading || reportLoading ? '● Analyzing...' : '● Ready'}
{messages.map(msg => (
{msg.role === 'user' && (
{msg.content}
)} {msg.role === 'system' && (
{msg.content.split('\n').map((line, i) => ( {line}
))}
)} {msg.role === 'assistant' && (
{msg.content && msg.content.split('\n').map((line, i) => ( {line.startsWith('**') && line.endsWith('**') ? {line.replace(/\*\*/g, '')} : line}
))} {msg.strategyConfig && (
📋 Strategy Configuration
{Object.entries(msg.strategyConfig).map(([key, val]) => (
{key} {String(val)}
))}
)} {msg.executionPlan && (
🔧 Execution Plan
{msg.executionPlan.map(step => (
{step.step}
{step.tool} {step.desc}
))}
)} {msg.riskReport && (
📊 AI Risk Analysis Report
{renderRiskScore(msg.riskReport.risk_score)}
{msg.riskReport.sections.map((section, i) => (
{section.icon} {section.title}
    {section.insights.map((insight, j) => (
  • $1') .replace(/`(.*?)`/g, '$1') }} /> ))}
))}
)}
)}
))} {(isLoading || reportLoading) && (
)}
{/* Example prompts */} {messages.length <= 1 && (
{EXAMPLE_PROMPTS.map((prompt, i) => ( ))}
)}