import { useEffect, useMemo, useState } from "react"; type JobStatus = "queued" | "running" | "completed" | "failed"; type ProviderOption = { id: string; base_url: string; models: string[]; }; type OptionsResponse = { providers: ProviderOption[]; analysts: string[]; research_depths: number[]; }; type AnalysisJob = { id: string; status: JobStatus; created_at: string; started_at?: string; completed_at?: string; request: { ticker: string; analysis_date: string; analysts: string[]; research_depth: number; llm_provider: string; quick_think_llm?: string; deep_think_llm?: string; backend_url?: string; google_thinking_level?: string; openai_reasoning_effort?: string; }; result?: { ticker: string; analysis_date: string; decision: string; final_trade_decision: string; investment_plan: string; reports: Record; }; error?: string; }; const API_BASE = import.meta.env.VITE_API_BASE_URL || "http://127.0.0.1:8000"; function todayIso(): string { return new Date().toISOString().slice(0, 10); } export default function App() { const [options, setOptions] = useState(null); const [loadingOptions, setLoadingOptions] = useState(true); const [optionsError, setOptionsError] = useState(null); const [ticker, setTicker] = useState("SPY"); const [analysisDate, setAnalysisDate] = useState(todayIso()); const [researchDepth, setResearchDepth] = useState(1); const [provider, setProvider] = useState("google"); const [selectedAnalysts, setSelectedAnalysts] = useState([ "market", "social", "news", "fundamentals", ]); const [quickThink, setQuickThink] = useState(""); const [deepThink, setDeepThink] = useState(""); const [googleThinkingLevel, setGoogleThinkingLevel] = useState("high"); const [openaiReasoningEffort, setOpenaiReasoningEffort] = useState("medium"); const [backendUrlOverride, setBackendUrlOverride] = useState(""); const [job, setJob] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); const providers = options?.providers ?? []; const providerMeta = useMemo( () => providers.find((item) => item.id === provider), [provider, providers] ); useEffect(() => { let mounted = true; (async () => { try { setOptionsError(null); const response = await fetch(`${API_BASE}/api/options`); if (!response.ok) { throw new Error(`Failed to load options (${response.status})`); } const data: OptionsResponse = await response.json(); if (!mounted) { return; } setOptions(data); const google = data.providers.find((item) => item.id === "google"); if (google && google.models.length > 0) { setQuickThink(google.models[0]); setDeepThink(google.models[0]); } } catch (error) { if (mounted) { const message = error instanceof Error ? error.message : "Failed to load options"; setOptionsError(message); } } finally { if (mounted) { setLoadingOptions(false); } } })(); return () => { mounted = false; }; }, []); useEffect(() => { if (!providerMeta?.models?.length) { return; } if (!providerMeta.models.includes(quickThink)) { setQuickThink(providerMeta.models[0]); } if (!providerMeta.models.includes(deepThink)) { setDeepThink(providerMeta.models[0]); } }, [providerMeta, quickThink, deepThink]); useEffect(() => { if (!job || (job.status !== "queued" && job.status !== "running")) { return; } const timer = setInterval(async () => { try { const response = await fetch(`${API_BASE}/api/analysis/jobs/${job.id}`); if (!response.ok) { throw new Error(`Polling failed (${response.status})`); } const updated = (await response.json()) as AnalysisJob; setJob(updated); } catch (error) { const message = error instanceof Error ? error.message : "Polling failed"; setJob((current) => { if (!current) { return current; } return { ...current, status: "failed", error: message, completed_at: new Date().toISOString(), }; }); } }, 2500); return () => clearInterval(timer); }, [job]); const formValid = useMemo(() => { const normalizedTicker = ticker.trim(); return ( normalizedTicker.length > 0 && analysisDate.length === 10 && selectedAnalysts.length > 0 && provider.length > 0 ); }, [ticker, analysisDate, selectedAnalysts, provider]); const toggleAnalyst = (value: string) => { setSelectedAnalysts((current) => { if (current.includes(value)) { const next = current.filter((item) => item !== value); return next.length ? next : current; } return [...current, value]; }); }; const submitJob = async (event: React.FormEvent) => { event.preventDefault(); if (!formValid) { setSubmitError("Complete ticker, date, and analyst selection before submitting."); return; } setIsSubmitting(true); setSubmitError(null); setJob(null); try { const payload = { ticker: ticker.trim().toUpperCase(), analysis_date: analysisDate, analysts: selectedAnalysts, research_depth: researchDepth, llm_provider: provider, backend_url: backendUrlOverride.trim() || providerMeta?.base_url, quick_think_llm: quickThink, deep_think_llm: deepThink, google_thinking_level: provider === "google" ? googleThinkingLevel : null, openai_reasoning_effort: provider === "openai" ? openaiReasoningEffort : null, }; const response = await fetch(`${API_BASE}/api/analysis/jobs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`Request failed (${response.status})`); } const created = (await response.json()) as AnalysisJob; setJob(created); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; setSubmitError(message); } finally { setIsSubmitting(false); } }; return (

TradingAgents Web Console

Keep the current backend logic intact and orchestrate runs through HTTP APIs. Submit a job, monitor state transitions, and inspect the final decision package.

React + FastAPI

Execution

API: {API_BASE}

{job ? ( <>
{job.status}

Job ID: {job.id}

{job.request.ticker} on {job.request.analysis_date} with {job.request.llm_provider}

{job.status === "failed" && (
{job.error || "Job failed"}
)} {job.result && (

Decision

{job.result.decision}

Final Trade Decision

{String(job.result.final_trade_decision || "")}

Investment Plan

{String(job.result.investment_plan || "")}
{Object.entries(job.result.reports || {}).map(([key, value]) => (

{key}

{String(value || "")}
))}
)} ) : (

Submit a run to see live job status and result artifacts here.

)}
); }