From 0690f628ab0b6caa3128e89a93c5c54a3210f5b8 Mon Sep 17 00:00:00 2001 From: Ali AL OGAILI Date: Mon, 23 Mar 2026 05:18:06 +0100 Subject: [PATCH] feat: update UI styles and add lightningcss dependency - Introduced lightningcss as a dependency for enhanced styling capabilities. - Updated global CSS variables to reflect a new design theme, including background, border, and text colors. - Modified layout components to incorporate new fonts and improved spacing. - Enhanced sidebar and dashboard layouts with subtle background textures and improved responsiveness. - Refined button styles and added new animations for a more dynamic user experience. - Improved the Run History Table with a new grid layout and status indicators. --- ui/app/(dashboard)/history/page.tsx | 51 ++- ui/app/(dashboard)/layout.tsx | 17 +- ui/app/(dashboard)/new-run/page.tsx | 17 +- ui/app/(dashboard)/runs/[id]/page.tsx | 70 ++-- ui/app/globals.css | 299 +++++++++++++----- ui/app/layout.tsx | 20 +- ui/components/Sidebar.tsx | 199 ++++++++---- .../history/components/RunHistoryTable.tsx | 259 ++++++++++----- .../new-run/components/AnalystSelector.tsx | 98 ++++-- .../new-run/components/RunConfigForm.tsx | 262 +++++++-------- .../run-detail/components/AnalystReports.tsx | 239 +++++++------- .../run-detail/components/PhaseTabs.tsx | 89 ++++-- .../run-detail/components/PipelineStepper.tsx | 253 ++++++++++----- .../run-detail/components/VerdictBanner.tsx | 227 ++++++++----- ui/package-lock.json | 14 +- ui/package.json | 1 + 16 files changed, 1352 insertions(+), 763 deletions(-) diff --git a/ui/app/(dashboard)/history/page.tsx b/ui/app/(dashboard)/history/page.tsx index 5864bb4b..119d23fd 100644 --- a/ui/app/(dashboard)/history/page.tsx +++ b/ui/app/(dashboard)/history/page.tsx @@ -5,13 +5,25 @@ import RunHistoryTable from '@/features/history/components/RunHistoryTable' export default function HistoryPage() { const { runs, loading, error } = useRunHistory() + return (
-
+ {/* Header */} +
+
+ Execution Log +

Run History

@@ -19,21 +31,40 @@ export default function HistoryPage() { Comprehensive log of all agent execution cycles

- - + New Analysis + + + + + + New Analysis
{loading && ( -

- Loading... -

+
+
+ + Loading runs… + +
)} + {error && ( -

+

{error} -

+
)} + {!loading && }
) diff --git a/ui/app/(dashboard)/layout.tsx b/ui/app/(dashboard)/layout.tsx index f25fca27..0f446ce6 100644 --- a/ui/app/(dashboard)/layout.tsx +++ b/ui/app/(dashboard)/layout.tsx @@ -4,7 +4,22 @@ export default function DashboardLayout({ children }: { children: React.ReactNod return (
-
{children}
+
+ {/* Subtle mesh background */} +
+
+ {children} +
+
) } diff --git a/ui/app/(dashboard)/new-run/page.tsx b/ui/app/(dashboard)/new-run/page.tsx index 12528e7d..f4730ec6 100644 --- a/ui/app/(dashboard)/new-run/page.tsx +++ b/ui/app/(dashboard)/new-run/page.tsx @@ -2,27 +2,28 @@ import RunConfigForm from '@/features/new-run/components/RunConfigForm' export default function NewRunPage() { return ( -
+
{/* Page header */}
-
+
Intelligence Engine

New Analysis

Configure a multi-agent analysis run. Your AI team will research market data, debate investment thesis, and deliver a structured decision. diff --git a/ui/app/(dashboard)/runs/[id]/page.tsx b/ui/app/(dashboard)/runs/[id]/page.tsx index 501b8ce2..4ea8b36e 100644 --- a/ui/app/(dashboard)/runs/[id]/page.tsx +++ b/ui/app/(dashboard)/runs/[id]/page.tsx @@ -7,11 +7,13 @@ import PhaseTabs from '@/features/run-detail/components/PhaseTabs' import { getRun } from '@/lib/api-client' import type { RunSummary } from '@/lib/types/run' -const STATUS_CONFIG: Record = { - connecting: { bg: 'var(--bg-elevated)', color: 'var(--text-mid)', dot: 'var(--text-low)', label: 'Connecting' }, - running: { bg: 'var(--hold-bg)', color: 'var(--hold)', dot: 'var(--hold)', label: 'Running' }, - complete: { bg: 'var(--buy-bg)', color: 'var(--buy)', dot: 'var(--buy)', label: 'Complete' }, - error: { bg: 'var(--error-bg)', color: 'var(--error)', dot: 'var(--error)', label: 'Error' }, +const STATUS_CONFIG: Record = { + connecting: { bg: 'var(--bg-elevated)', color: 'var(--text-mid)', dot: 'var(--text-low)', label: 'Connecting', pulse: false }, + running: { bg: 'var(--hold-bg)', color: 'var(--hold)', dot: 'var(--hold)', label: 'Running', pulse: true }, + complete: { bg: 'var(--buy-bg)', color: 'var(--buy)', dot: 'var(--buy)', label: 'Complete', pulse: false }, + error: { bg: 'var(--error-bg)', color: 'var(--error)', dot: 'var(--error)', label: 'Error', pulse: false }, } export default function RunDetailPage({ params }: { params: Promise<{ id: string }> }) { @@ -29,51 +31,60 @@ export default function RunDetailPage({ params }: { params: Promise<{ id: string

{/* Header */} -
+
-
+
Analysis Run

{run ? ( <> - {run.ticker} - · - {run.date} + + {run.ticker} + + · + + {run.date} + ) : ( - Loading… + Loading… )}

- {/* Status badge */} + {/* Status pill */}
- {sc.label} + {sc.label.toUpperCase()}
@@ -83,14 +94,14 @@ export default function RunDetailPage({ params }: { params: Promise<{ id: string {/* Error */} {error && (
- Error: {error} + Error: {error}
)} @@ -101,7 +112,6 @@ export default function RunDetailPage({ params }: { params: Promise<{ id: string {/* Phase tabs + reports */} -
) } diff --git a/ui/app/globals.css b/ui/app/globals.css index d371ca42..e33c72f6 100644 --- a/ui/app/globals.css +++ b/ui/app/globals.css @@ -1,57 +1,63 @@ @import "tailwindcss"; -/* ─── APEX Design Tokens ────────────────────────────────────────── */ +/* ─── OBSIDIAN Design Tokens ─────────────────────────────────────── */ :root { /* Backgrounds */ - --bg-base: #040C1A; - --bg-surface: #070F1C; - --bg-card: #0C1628; - --bg-elevated: #121E30; - --bg-hover: #182338; - --bg-active: #1E2E42; - --bg-sidebar: #030810; + --bg-base: #050508; + --bg-surface: #08080F; + --bg-card: #0C0C18; + --bg-elevated: #111120; + --bg-hover: #17172A; + --bg-active: #1D1D38; + --bg-sidebar: #030306; /* Borders */ - --border: rgba(82, 122, 196, 0.10); - --border-raised: rgba(82, 122, 196, 0.18); - --border-active: rgba(68, 128, 255, 0.40); - --border-accent: rgba(68, 128, 255, 0.60); + --border: rgba(80, 80, 200, 0.07); + --border-raised: rgba(80, 80, 200, 0.14); + --border-active: rgba(0, 200, 240, 0.28); + --border-accent: rgba(0, 200, 240, 0.55); /* Text */ - --text-high: #E0E8FF; - --text-mid: #7A8FAD; - --text-low: #354869; - --text-faint: #1C2A40; + --text-high: #F0F2FF; + --text-mid: #525E80; + --text-low: #202840; + --text-faint: #0E1220; - /* Accent Blue */ - --accent: #4480FF; - --accent-light: #8AAFFF; - --accent-dim: #1A3A88; - --accent-glow: rgba(68, 128, 255, 0.14); - --accent-glow2: rgba(68, 128, 255, 0.06); + /* Accent Cyan */ + --accent: #00C4E8; + --accent-light: #65DAFF; + --accent-dim: #00243A; + --accent-glow: rgba(0, 196, 232, 0.14); + --accent-glow2: rgba(0, 196, 232, 0.05); - /* Semantic */ - --buy: #00CE68; - --buy-bg: rgba(0, 206, 104, 0.08); - --buy-ring: rgba(0, 206, 104, 0.25); - --sell: #FF3355; - --sell-bg: rgba(255, 51, 85, 0.08); - --sell-ring:rgba(255, 51, 85, 0.25); - --hold: #F59E0B; - --hold-bg: rgba(245, 158, 11, 0.08); - --hold-ring:rgba(245, 158, 11, 0.25); - --error: #FF4444; - --error-bg: rgba(255, 68, 68, 0.08); + /* Gold (premium highlight) */ + --gold: #FFB400; + --gold-bg: rgba(255, 180, 0, 0.07); + --gold-dim: #271D00; + --gold-ring: rgba(255, 180, 0, 0.25); + + /* Semantic — BUY / SELL / HOLD */ + --buy: #00E078; + --buy-bg: rgba(0, 224, 120, 0.07); + --buy-ring: rgba(0, 224, 120, 0.22); + --sell: #FF1F4C; + --sell-bg: rgba(255, 31, 76, 0.07); + --sell-ring: rgba(255, 31, 76, 0.22); + --hold: #FFB400; + --hold-bg: rgba(255, 180, 0, 0.07); + --hold-ring: rgba(255, 180, 0, 0.22); + --error: #FF2B3E; + --error-bg: rgba(255, 43, 62, 0.07); /* Status */ - --status-running: #F59E0B; - --status-done: #4480FF; - --status-pending: #1C2A40; + --status-running: #FFB400; + --status-done: #00C4E8; + --status-pending: #141828; } -/* ─── Animations ─────────────────────────────────────────────────── */ +/* ─── Keyframes ──────────────────────────────────────────────────── */ @keyframes fadeUp { - from { opacity: 0; transform: translateY(10px); } + from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } @@ -61,8 +67,8 @@ } @keyframes shimmer { - 0%, 100% { opacity: 0.35; } - 50% { opacity: 0.9; } + 0%, 100% { opacity: 0.25; } + 50% { opacity: 1; } } @keyframes spin-slow { @@ -70,8 +76,8 @@ } @keyframes pulse-glow { - 0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); } - 50% { box-shadow: 0 0 0 6px var(--accent-glow); } + 0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); } + 50% { box-shadow: 0 0 0 8px var(--accent-glow); } } @keyframes scan-line { @@ -79,6 +85,45 @@ to { transform: translateX(200%); } } +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-6px); } +} + +@keyframes glow-breathe { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} + +@keyframes data-flicker { + 0%, 95%, 100% { opacity: 1; } + 96% { opacity: 0.6; } + 97% { opacity: 1; } + 98% { opacity: 0.75; } + 99% { opacity: 1; } +} + +@keyframes verdict-reveal { + 0% { opacity: 0; transform: scale(0.88) translateY(10px); filter: blur(4px); } + 100% { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); } +} + +@keyframes orbit { + from { transform: rotate(0deg) translateX(24px) rotate(0deg); } + to { transform: rotate(360deg) translateX(24px) rotate(-360deg); } +} + +@keyframes gradient-shift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +@keyframes step-complete { + 0% { transform: scale(0.5); opacity: 0; } + 60% { transform: scale(1.2); } + 100% { transform: scale(1); opacity: 1; } +} + /* ─── Base ───────────────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; } @@ -91,34 +136,68 @@ html, body { font-feature-settings: "cv02", "cv03", "cv04", "cv11"; } +/* ─── Background Textures ─────────────────────────────────────────── */ +.dot-grid { + background-image: radial-gradient(circle, rgba(100, 100, 255, 0.12) 1px, transparent 1px); + background-size: 24px 24px; +} + +.mesh-bg { + background: + radial-gradient(ellipse 60% 40% at 20% 20%, rgba(0, 196, 232, 0.05) 0%, transparent 60%), + radial-gradient(ellipse 50% 60% at 80% 70%, rgba(80, 40, 200, 0.06) 0%, transparent 60%), + radial-gradient(ellipse 40% 50% at 50% 50%, rgba(255, 180, 0, 0.03) 0%, transparent 70%); +} + /* ─── Typography ─────────────────────────────────────────────────── */ .apex-display { - font-family: var(--font-manrope, 'Manrope'), sans-serif; - font-weight: 700; - letter-spacing: -0.03em; + font-family: var(--font-syne, 'Syne'), var(--font-manrope, 'Manrope'), sans-serif; + font-weight: 800; + letter-spacing: -0.04em; color: var(--text-high); } .apex-label { font-size: 10px; - font-weight: 600; - letter-spacing: 0.12em; + font-weight: 700; + letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-mid); font-family: var(--font-manrope, 'Manrope'), sans-serif; } +.terminal-text { + font-family: var(--font-mono, 'JetBrains Mono'), 'Courier New', monospace; + font-feature-settings: "tnum"; + font-variant-numeric: tabular-nums; +} + +.gradient-text { + background: linear-gradient(135deg, var(--text-high) 0%, var(--accent-light) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + /* ─── Cards ──────────────────────────────────────────────────────── */ .apex-card { background: var(--bg-card); border: 1px solid var(--border); - border-radius: 10px; + border-radius: 12px; } .apex-card-elevated { background: var(--bg-elevated); border: 1px solid var(--border-raised); - border-radius: 10px; + border-radius: 12px; +} + +.glass-card { + background: rgba(12, 12, 28, 0.7); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--border-raised); + border-radius: 12px; } /* ─── Buttons ────────────────────────────────────────────────────── */ @@ -127,34 +206,36 @@ html, body { overflow: hidden; display: inline-flex; align-items: center; - gap: 6px; + gap: 7px; background: var(--accent); - color: #fff; - font-weight: 600; + color: #020508; + font-weight: 700; font-size: 13px; - letter-spacing: 0.01em; + letter-spacing: 0.02em; border-radius: 8px; - padding: 9px 20px; - transition: background 0.15s, opacity 0.15s, box-shadow 0.15s; + padding: 10px 22px; + transition: background 0.15s, opacity 0.15s, box-shadow 0.2s, transform 0.1s; font-family: var(--font-manrope, 'Manrope'), sans-serif; cursor: pointer; } -.btn-primary::after { +.btn-primary::before { content: ''; position: absolute; inset: 0; - background: linear-gradient(135deg, rgba(255,255,255,0.12) 0%, transparent 60%); + background: linear-gradient(135deg, rgba(255,255,255,0.18) 0%, transparent 55%); pointer-events: none; } .btn-primary:hover { - background: #5590FF; - box-shadow: 0 4px 16px rgba(68,128,255,0.35); + background: #28DAFF; + box-shadow: 0 0 0 4px rgba(0, 196, 232, 0.18), 0 8px 24px rgba(0, 196, 232, 0.28); + transform: translateY(-1px); } -.btn-primary:active { opacity: 0.85; } +.btn-primary:active { opacity: 0.85; transform: translateY(0); } .btn-primary:disabled { - opacity: 0.35; + opacity: 0.3; cursor: not-allowed; box-shadow: none; + transform: none; } .btn-secondary { @@ -163,12 +244,12 @@ html, body { gap: 6px; background: var(--bg-elevated); color: var(--text-mid); - font-weight: 500; + font-weight: 600; font-size: 13px; border: 1px solid var(--border-raised); border-radius: 8px; - padding: 8px 16px; - transition: background 0.15s, color 0.15s, border-color 0.15s; + padding: 9px 18px; + transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s; font-family: var(--font-manrope, 'Manrope'), sans-serif; cursor: pointer; } @@ -176,10 +257,29 @@ html, body { background: var(--bg-hover); color: var(--text-high); border-color: var(--border-active); + transform: translateY(-1px); +} +.btn-ghost { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--bg-elevated); + color: var(--text-mid); + font-weight: 600; + font-size: 13px; + border: 1px solid var(--border-raised); + border-radius: 8px; + padding: 9px 18px; + transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s; + font-family: var(--font-manrope, 'Manrope'), sans-serif; + cursor: pointer; +} +.btn-ghost:hover { + background: var(--bg-hover); + color: var(--text-high); + border-color: var(--border-active); + transform: translateY(-1px); } - -/* backwards compat aliases */ -.btn-ghost { @apply btn-secondary; } /* ─── Inputs ─────────────────────────────────────────────────────── */ .vault-input { @@ -188,10 +288,10 @@ html, body { color: var(--text-high); font-size: 13px; border: 1px solid var(--border-raised); - border-radius: 6px; - padding: 9px 12px; + border-radius: 8px; + padding: 10px 14px; outline: none; - transition: border-color 0.15s, background 0.15s, box-shadow 0.15s; + transition: border-color 0.15s, background 0.15s, box-shadow 0.2s; font-family: var(--font-manrope, 'Manrope'), sans-serif; } .vault-input::placeholder { color: var(--text-low); } @@ -202,15 +302,23 @@ html, body { } .vault-input option { background: var(--bg-elevated); } +/* date input calendar icon color */ +.vault-input[type="date"]::-webkit-calendar-picker-indicator { + filter: invert(0.5) sepia(1) saturate(2) hue-rotate(180deg); + opacity: 0.5; + cursor: pointer; +} + /* ─── Badges ─────────────────────────────────────────────────────── */ .badge-buy { display: inline-flex; align-items: center; background: var(--buy-bg); color: var(--buy); border: 1px solid var(--buy-ring); - font-size: 11px; font-weight: 700; - padding: 2px 9px; border-radius: 99px; - letter-spacing: 0.04em; + font-size: 10px; font-weight: 800; + padding: 2px 10px; border-radius: 99px; + letter-spacing: 0.08em; + text-transform: uppercase; font-family: var(--font-manrope, 'Manrope'), sans-serif; } .badge-sell { @@ -218,9 +326,10 @@ html, body { background: var(--sell-bg); color: var(--sell); border: 1px solid var(--sell-ring); - font-size: 11px; font-weight: 700; - padding: 2px 9px; border-radius: 99px; - letter-spacing: 0.04em; + font-size: 10px; font-weight: 800; + padding: 2px 10px; border-radius: 99px; + letter-spacing: 0.08em; + text-transform: uppercase; font-family: var(--font-manrope, 'Manrope'), sans-serif; } .badge-hold { @@ -228,22 +337,40 @@ html, body { background: var(--hold-bg); color: var(--hold); border: 1px solid var(--hold-ring); - font-size: 11px; font-weight: 700; - padding: 2px 9px; border-radius: 99px; - letter-spacing: 0.04em; + font-size: 10px; font-weight: 800; + padding: 2px 10px; border-radius: 99px; + letter-spacing: 0.08em; + text-transform: uppercase; font-family: var(--font-manrope, 'Manrope'), sans-serif; } +/* ─── Horizontal Rule ────────────────────────────────────────────── */ +.apex-rule { + height: 1px; + background: linear-gradient(90deg, transparent, var(--border-raised), transparent); + border: none; +} + /* ─── Scrollbar ──────────────────────────────────────────────────── */ -::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar { width: 4px; height: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border-raised); border-radius: 99px; } ::-webkit-scrollbar-thumb:hover { background: var(--border-active); } /* ─── Selection ──────────────────────────────────────────────────── */ -::selection { background: var(--accent-glow); color: var(--text-high); } +::selection { background: rgba(0, 196, 232, 0.20); color: var(--text-high); } -/* ─── Fade animations for components ────────────────────────────── */ -.animate-fade-up { animation: fadeUp 0.3s ease-out both; } -.animate-fade-in { animation: fadeIn 0.25s ease-out both; } -.animate-shimmer { animation: shimmer 1.6s ease-in-out infinite; } +/* ─── Animations ─────────────────────────────────────────────────── */ +.animate-fade-up { animation: fadeUp 0.35s cubic-bezier(0.16,1,0.3,1) both; } +.animate-fade-in { animation: fadeIn 0.25s ease-out both; } +.animate-shimmer { animation: shimmer 1.6s ease-in-out infinite; } +.animate-float { animation: float 4s ease-in-out infinite; } +.animate-glow { animation: glow-breathe 2.5s ease-in-out infinite; } +.animate-flicker { animation: data-flicker 8s ease-in-out infinite; } + +/* Staggered fade-up delays */ +.delay-50 { animation-delay: 50ms; } +.delay-100 { animation-delay: 100ms; } +.delay-150 { animation-delay: 150ms; } +.delay-200 { animation-delay: 200ms; } +.delay-300 { animation-delay: 300ms; } diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx index 176faeab..2b653e54 100644 --- a/ui/app/layout.tsx +++ b/ui/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next' -import { Manrope, Inter } from 'next/font/google' +import { Manrope, Syne, JetBrains_Mono } from 'next/font/google' import './globals.css' const manrope = Manrope({ @@ -8,15 +8,21 @@ const manrope = Manrope({ weight: ['400', '500', '600', '700', '800'], }) -const inter = Inter({ - variable: '--font-inter', +const syne = Syne({ + variable: '--font-syne', subsets: ['latin'], - weight: ['400', '500', '600'], + weight: ['700', '800'], +}) + +const jetbrainsMono = JetBrains_Mono({ + variable: '--font-mono', + subsets: ['latin'], + weight: ['400', '500', '600', '700'], }) export const metadata: Metadata = { - title: 'TradingAgents', - description: 'Multi-agent trading analysis', + title: 'TradingAgents — Multi-Agent AI Analysis', + description: 'Institutional-grade multi-agent trading intelligence', } export default function RootLayout({ @@ -27,7 +33,7 @@ export default function RootLayout({ return ( {children} diff --git a/ui/components/Sidebar.tsx b/ui/components/Sidebar.tsx index d8d7a4a8..b796109f 100644 --- a/ui/components/Sidebar.tsx +++ b/ui/components/Sidebar.tsx @@ -6,31 +6,33 @@ const NAV = [ { href: '/new-run', label: 'New Analysis', + tag: 'RUN', icon: ( - - - + + ), }, { href: '/history', label: 'Run History', + tag: 'LOG', icon: ( - - - - + + + + ), }, { href: '/settings', label: 'Settings', + tag: 'CFG', icon: ( - - - + + + ), }, @@ -41,70 +43,105 @@ export default function Sidebar() { return (
-
- {/* Geometric logo mark */} + className="absolute inset-0 pointer-events-none" + style={{ + backgroundImage: 'radial-gradient(circle, rgba(80,80,200,0.08) 1px, transparent 1px)', + backgroundSize: '20px 20px', + }} + /> + + {/* Top glow */} +
+ + {/* Logo */} +
+
+ {/* Logo mark */}
- - - + + + + {/* Inner glow */} +
+
TradingAgents
- Multi-Agent AI + + MULTI-AGENT AI
- {/* Nav section */} -
-
- Navigation -
+ {/* Nav */} +
+
Workspace
+
+ {/* Divider */} +
+ {/* Footer */} -
-
+
+
- - Local · Development - + + + + +
+
+
+ Local Instance +
+
+ + DEV · PORT 8000 +
+
diff --git a/ui/features/history/components/RunHistoryTable.tsx b/ui/features/history/components/RunHistoryTable.tsx index e6ab6391..68622dd1 100644 --- a/ui/features/history/components/RunHistoryTable.tsx +++ b/ui/features/history/components/RunHistoryTable.tsx @@ -5,118 +5,203 @@ type Props = { runs: RunSummary[] } function DecisionBadge({ decision }: { decision: string }) { const lower = decision.toLowerCase() - if (lower === 'buy') return {decision} + if (lower === 'buy') return {decision} if (lower === 'sell') return {decision} if (lower === 'hold') return {decision} return ( {decision} ) } +function StatusDot({ decision }: { decision?: string }) { + const lower = decision?.toLowerCase() + const color = lower === 'buy' ? 'var(--buy)' : lower === 'sell' ? 'var(--sell)' : lower === 'hold' ? 'var(--hold)' : 'var(--text-low)' + return ( +
+ ) +} + export default function RunHistoryTable({ runs }: Props) { if (runs.length === 0) { return (
-

- No runs yet. Start a new analysis. -

+ {/* Empty state icon */} +
+ + + + + +
+
+

+ No analysis runs yet +

+

+ Start your first analysis to see results here +

+
+ + + + + New Analysis +
) } + return (
- - - - - - - - - - - {runs.map((run) => ( - - (e.currentTarget.style.backgroundColor = 'var(--bg-hover)') - } - onMouseLeave={(e) => - (e.currentTarget.style.backgroundColor = '') - } - > - - - - - - - ))} - -
- Ticker - - Date - - Decision - - Created - -
+ {['Ticker', 'Date', 'Decision', 'Created', ''].map((h) => ( +
+ {h} +
+ ))} + + + {/* Rows */} +
+ {runs.map((run, idx) => ( +
(e.currentTarget.style.background = 'var(--bg-hover)')} + onMouseLeave={(e) => (e.currentTarget.style.background = '')} + > + {/* Ticker */} +
+ + {run.ticker} -
- {run.date} - - {run.decision ? ( - - ) : ( - - )} - - {new Date(run.created_at).toLocaleString()} - - - (e.currentTarget.style.color = 'var(--accent-light)') - } - onMouseLeave={(e) => - (e.currentTarget.style.color = 'var(--accent)') - } + + + + {/* Date */} +
+ {run.date} +
+ + {/* Decision */} +
+ {run.decision ? ( + + ) : ( + - View Report → - -
+ PENDING + + )} +
+ + {/* Created */} +
+ {new Date(run.created_at).toLocaleString()} +
+ + {/* Action */} +
+ { (e.currentTarget as HTMLElement).style.color = 'var(--accent-light)' }} + onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.color = 'var(--accent)' }} + > + View + + + + +
+
+ ))} +
+ + {/* Footer */} +
+ + {runs.length} RUN{runs.length !== 1 ? 'S' : ''} TOTAL + + + TRADINGAGENTS · LOCAL + +
) } diff --git a/ui/features/new-run/components/AnalystSelector.tsx b/ui/features/new-run/components/AnalystSelector.tsx index cb0c8b30..6f2aea12 100644 --- a/ui/features/new-run/components/AnalystSelector.tsx +++ b/ui/features/new-run/components/AnalystSelector.tsx @@ -6,28 +6,56 @@ const ANALYSTS = [ label: 'Market', full: 'Market Analyst', desc: 'Price action & technicals', - dot: '#4480FF', + accent: '#00C4E8', + icon: ( + + + + + ), }, { id: 'news', label: 'News', full: 'News Analyst', desc: 'Sentiment & headlines', - dot: '#A78BFA', + accent: '#A78BFA', + icon: ( + + + + + + ), }, { id: 'fundamentals', label: 'Fundamentals', full: 'Fundamentals Analyst', desc: 'Earnings & financials', - dot: '#00CE68', + accent: '#00E078', + icon: ( + + + + + + ), }, { id: 'social', label: 'Social', full: 'Social Analyst', desc: 'Social media signals', - dot: '#F59E0B', + accent: '#FFB400', + icon: ( + + + + + + + ), }, ] @@ -43,39 +71,63 @@ export default function AnalystSelector({ selected, onChange }: Props) { return (
- {ANALYSTS.map(({ id, label, desc, dot, full }) => { + {ANALYSTS.map(({ id, label, desc, accent, icon, full }) => { const active = selected.includes(id) + return ( ) })} diff --git a/ui/features/run-detail/components/PipelineStepper.tsx b/ui/features/run-detail/components/PipelineStepper.tsx index 8cd0539b..10a05c12 100644 --- a/ui/features/run-detail/components/PipelineStepper.tsx +++ b/ui/features/run-detail/components/PipelineStepper.tsx @@ -6,11 +6,12 @@ type Phase = 'analysts' | 'researchers' | 'trader' | 'risk' type Props = { steps: Record } const PHASES: Phase[] = ['analysts', 'researchers', 'trader', 'risk'] -const PHASE_LABELS: Record = { - analysts: 'Analysts', researchers: 'Researchers', trader: 'Trader', risk: 'Risk', -} -const PHASE_NUMS: Record = { - analysts: '01', researchers: '02', trader: '03', risk: '04', + +const PHASE_META: Record = { + analysts: { label: 'Analysis', code: 'PHASE-01', desc: 'Market data & signals' }, + researchers: { label: 'Research', code: 'PHASE-02', desc: 'Bull/bear debate' }, + trader: { label: 'Trade Plan', code: 'PHASE-03', desc: 'Strategy formulation' }, + risk: { label: 'Risk Review', code: 'PHASE-04', desc: 'Risk-adjusted decision' }, } function phaseStatus(phase: Phase, steps: Record): StepStatus { @@ -21,112 +22,190 @@ function phaseStatus(phase: Phase, steps: Record): StepStatu } export default function PipelineStepper({ steps }: Props) { + const doneCount = PHASES.filter((p) => phaseStatus(p, steps) === 'done').length + const progressPct = (doneCount / PHASES.length) * 100 + return (
-
- Pipeline + {/* Header row */} +
+
Agent Pipeline
+
+ {doneCount} + / + {PHASES.length} + PHASES +
-
+ + {/* Progress track */} +
+
+ {/* Moving glow tip */} + {progressPct > 0 && progressPct < 100 && ( +
+ )} +
+ + {/* Scanline on active */} + {doneCount < PHASES.length && ( +
+ )} +
+ + {/* Phase nodes */} +
{PHASES.map((phase, i) => { const status = phaseStatus(phase, steps) const isDone = status === 'done' const isRunning = status === 'running' - const isPending = status === 'pending' + const meta = PHASE_META[phase] return ( -
- {/* Step node */} -
- {/* Circle */} +
+ {/* Node circle */} +
+ {isDone ? ( + + + + ) : isRunning ? ( +
+ ) : ( + + {String(i + 1).padStart(2, '0')} + + )} +
+ + {/* Labels */} +
- {isDone ? ( - - - - ) : isRunning ? ( -
- ) : ( - - {PHASE_NUMS[phase]} - - )} + {meta.label}
- - {/* Label */} - - {PHASE_LABELS[phase]} - + {meta.desc} +
- {/* Connector */} - {i < PHASES.length - 1 && ( -
- {isRunning && ( -
- )} -
- )} + {/* Phase code */} +
+ {meta.code} +
) })} diff --git a/ui/features/run-detail/components/VerdictBanner.tsx b/ui/features/run-detail/components/VerdictBanner.tsx index 83d825df..da524cca 100644 --- a/ui/features/run-detail/components/VerdictBanner.tsx +++ b/ui/features/run-detail/components/VerdictBanner.tsx @@ -6,33 +6,41 @@ const VERDICT_CONFIG: Record = { BUY: { - color: 'var(--buy)', - colorBg: 'var(--buy-bg)', - colorRing: 'var(--buy-ring)', - label: 'BUY', - sublabel: 'Long position recommended', - arrow: '↑', + color: 'var(--buy)', + colorBg: 'var(--buy-bg)', + colorRing: 'var(--buy-ring)', + colorGlow: 'rgba(0, 224, 120, 0.15)', + label: 'BUY', + sublabel: 'Long Position', + symbol: '↑', + description: 'AI consensus recommends entering a long position', }, SELL: { - color: 'var(--sell)', - colorBg: 'var(--sell-bg)', - colorRing: 'var(--sell-ring)', - label: 'SELL', - sublabel: 'Exit position recommended', - arrow: '↓', + color: 'var(--sell)', + colorBg: 'var(--sell-bg)', + colorRing: 'var(--sell-ring)', + colorGlow: 'rgba(255, 31, 76, 0.15)', + label: 'SELL', + sublabel: 'Exit Position', + symbol: '↓', + description: 'AI consensus recommends exiting the position', }, HOLD: { - color: 'var(--hold)', - colorBg: 'var(--hold-bg)', - colorRing: 'var(--hold-ring)', - label: 'HOLD', - sublabel: 'Maintain current position', - arrow: '→', + color: 'var(--hold)', + colorBg: 'var(--hold-bg)', + colorRing: 'var(--hold-ring)', + colorGlow: 'rgba(255, 180, 0, 0.15)', + label: 'HOLD', + sublabel: 'Maintain Position', + symbol: '→', + description: 'AI consensus recommends maintaining current exposure', }, } @@ -42,99 +50,150 @@ export default function VerdictBanner({ verdict, ticker, date }: Props) { return (
- {/* Background glow */} + {/* Background glow blobs */}
+
-
- {/* Left: metadata */} -
-
+ {/* Scanline shimmer on active color */} +
+ + {/* Corner decorative mark */} +
+ {cfg.symbol} +
+ + {/* Main content */} +
+ {/* Top row: label + meta */} +
+
Analysis Complete
- {ticker} -
-
- {date} + AI CONSENSUS
- {/* Divider */} -
- - {/* Right: verdict */} -
-
+ {/* Main verdict row */} +
+ {/* Left: ticker + description */} +
+ + {ticker} + + + {date} + +
+

+ {cfg.description} +

+
+ + {/* Right: big verdict badge */} +
+
{cfg.sublabel}
- AI consensus decision + + {cfg.symbol} + + + {cfg.label} +
- - {/* Big verdict */} -
- - {cfg.arrow} - - - {cfg.label} - -
diff --git a/ui/package-lock.json b/ui/package-lock.json index 42cfcdda..815afde0 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,6 +8,7 @@ "name": "ui", "version": "0.1.0", "dependencies": { + "lightningcss": "^1.32.0", "next": "16.2.1", "react": "19.2.4", "react-dom": "19.2.4" @@ -4459,7 +4460,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7827,7 +7827,6 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -7860,7 +7859,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7881,7 +7879,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7902,7 +7899,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7923,7 +7919,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7944,7 +7939,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7965,7 +7959,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7986,7 +7979,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8007,7 +7999,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8028,7 +8019,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8049,7 +8039,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -8070,7 +8059,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ diff --git a/ui/package.json b/ui/package.json index 8654e481..89d6464f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "lightningcss": "^1.32.0", "next": "16.2.1", "react": "19.2.4", "react-dom": "19.2.4"