From 503a1c8bde66f0c3d20f5c73ae780c764d3c78ba Mon Sep 17 00:00:00 2001 From: Henry Winkel Date: Thu, 14 May 2026 08:12:12 +0200 Subject: [PATCH] feat: add analyze page with dataflow visualization --- app/components/Navbar.tsx | 6 ++ app/routes/analyze.tsx | 205 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 app/routes/analyze.tsx diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index a92e0a6..74d8040 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -19,6 +19,12 @@ export default function Navbar() { > Stocks + + Analyze + diff --git a/app/routes/analyze.tsx b/app/routes/analyze.tsx new file mode 100644 index 0000000..b173ca9 --- /dev/null +++ b/app/routes/analyze.tsx @@ -0,0 +1,205 @@ +import { useState } from "react"; +import Navbar from "../components/Navbar"; +import type { AgentSignal, AnalystReport, DebateRound, TradingDecision } from "../types/agents"; + +type AnalysisStep = "input" | "analysts" | "researchers" | "trader" | "decision"; + +interface AnalysisState { + ticker: string; + date: string; + analysts: AnalystReport[]; + debates: DebateRound[]; + decision: TradingDecision | null; + isLoading: boolean; + error: string | null; +} + +const SignalBadge = ({ signal }: { signal: AgentSignal["signal"] }) => { + const colors = { + bullish: "bg-green-100 text-green-800", + bearish: "bg-red-100 text-red-800", + neutral: "bg-gray-100 text-gray-800", + }; + return ( + + {signal} + + ); +}; + +export const meta = () => { + return [ + { title: "Analyze - AITrader" }, + { name: "description", content: "Multi-agent trading analysis with dataflow visualization" }, + ]; +}; + +export default function Analyze() { + const [state, setState] = useState({ + ticker: "", + date: new Date().toISOString().split("T")[0], + analysts: [], + debates: [], + decision: null, + isLoading: false, + error: null, + }); + + const [currentStep, setCurrentStep] = useState("input"); + + const runAnalysis = async (e: React.FormEvent) => { + e.preventDefault(); + setState((s) => ({ ...s, isLoading: true, error: null })); + + try { + const response = await fetch("/api/analyze", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ticker: state.ticker, date: state.date }), + }); + + if (!response.ok) throw new Error("Analysis failed"); + const decision = await response.json(); + + setState((s) => ({ + ...s, + decision, + isLoading: false, + })); + setCurrentStep("decision"); + } catch (err) { + setState((s) => ({ + ...s, + error: err instanceof Error ? err.message : "Unknown error", + isLoading: false, + })); + } + }; + + return ( +
+ +
+

Multi-Agent Trading Analysis

+ +
+

Input Parameters

+
+
+ + setState((s) => ({ ...s, ticker: e.target.value.toUpperCase() }))} + placeholder="AAPL" + className="border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500" + required + /> +
+
+ + setState((s) => ({ ...s, date: e.target.value }))} + className="border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500" + /> +
+
+ +
+
+
+ + {state.error && ( +
+ {state.error} +
+ )} + + {currentStep !== "input" && ( +
+
+ {["input", "analysts", "researchers", "trader", "decision"].map((step, i) => ( +
+
+ {i + 1} +
+ {i < 4 &&
} +
+ ))} +
+ + {state.analysts.length > 0 && ( +
+

Analyst Reports

+
+ {state.analysts.map((report, i) => ( +
+
+ {report.analyst} + +
+

{report.report}

+
+ ))} +
+
+ )} + + {state.debates.length > 0 && ( +
+

Research Debate

+ {state.debates.map((debate, i) => ( +
+
+ Bullish View: +

{debate.bullishView}

+
+
+ Bearish View: +

{debate.bearishView}

+
+
+ ))} +
+ )} + + {state.decision && ( +
+

Trading Decision

+
+ {state.decision.action} + Confidence: {(state.decision.confidence * 100).toFixed(0)}% +
+

{state.decision.reasoning}

+ +
+ Agent Signals: +
+ {state.decision.agentSignals.map((s, i) => ( +
+ {s.agent}: + +
+ ))} +
+
+
+ )} +
+ )} +
+
+ ); +} \ No newline at end of file