/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect, useMemo, useRef, useState } from 'react';
import {
Monitor,
Users,
TrendingUp,
Library,
Wallet,
Brain,
FileText,
LifeBuoy,
Bell,
CreditCard,
User,
Search,
ChevronUp,
Cpu,
BarChart3,
Globe,
Newspaper,
Zap,
Activity,
Calendar,
LineChart,
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import {
CrosshairMode,
ColorType,
LineStyle,
createChart,
type IChartApi,
type IPriceLine,
type ISeriesApi,
type SeriesMarker,
type Time,
} from 'lightweight-charts';
import {
api,
SPECIALIST_DISPLAY,
statusFromSignal,
useStockerEnv,
type CouncilDecision,
type EnvironmentState,
type MarketObservation,
type OhlcvBar,
type OhlcvResponse,
type Side,
type StockerEnv,
type TrainingMetrics,
} from './api';
type Tab = 'Terminal' | 'Council' | 'Training' | 'Gallery' | 'Portfolio' | 'Intelligence';
// --------------------------------------------------------------------- atoms
const SidebarItem = ({
icon: Icon,
label,
active,
onClick,
}: {
icon: any;
label: string;
active: boolean;
onClick: () => void;
}) => (
);
const AgentCard = ({
title,
icon: Icon,
stat,
color,
children,
active,
}: {
title: string;
icon: any;
stat?: string;
color: string;
children: any;
active?: boolean;
}) => (
);
// ---------------------------------------------------------------------- chart
export type ChartRange = '1M' | '3M' | '6M' | 'All';
const RANGE_BARS: Record, number> = {
'1M': 22,
'3M': 66,
'6M': 132,
};
const CandlestickChart = ({
bars,
currentDate,
currentPrice,
range,
}: {
bars: OhlcvBar[];
currentDate?: string | null;
currentPrice?: number | null;
range?: ChartRange;
}) => {
const containerRef = useRef(null);
const chartRef = useRef(null);
const candleRef = useRef | null>(null);
const volumeRef = useRef | null>(null);
const priceLineRef = useRef(null);
// mount once
useEffect(() => {
if (!containerRef.current) return;
const chart = createChart(containerRef.current, {
layout: {
background: { type: ColorType.Solid, color: 'transparent' },
textColor: '#a3a3a3',
fontFamily: 'JetBrains Mono, ui-monospace, monospace',
},
grid: {
vertLines: { color: 'rgba(255,255,255,0.04)' },
horzLines: { color: 'rgba(255,255,255,0.04)' },
},
timeScale: {
borderVisible: false,
timeVisible: false,
secondsVisible: false,
},
rightPriceScale: {
borderVisible: false,
scaleMargins: { top: 0.05, bottom: 0.28 },
},
crosshair: {
mode: CrosshairMode.Normal,
vertLine: { color: 'rgba(0,255,65,0.3)', width: 1, style: 3, labelBackgroundColor: '#00FF41' },
horzLine: { color: 'rgba(0,255,65,0.3)', width: 1, style: 3, labelBackgroundColor: '#00FF41' },
},
autoSize: false,
});
chartRef.current = chart;
candleRef.current = chart.addCandlestickSeries({
upColor: '#00FF41',
downColor: '#ffb4ab',
wickUpColor: '#00FF41',
wickDownColor: '#ffb4ab',
borderVisible: false,
});
volumeRef.current = chart.addHistogramSeries({
priceFormat: { type: 'volume' },
priceScaleId: '',
color: 'rgba(0,255,65,0.3)',
});
volumeRef.current.priceScale().applyOptions({
scaleMargins: { top: 0.78, bottom: 0 },
});
const ro = new ResizeObserver((entries) => {
const rect = entries[0]?.contentRect;
if (rect) chart.applyOptions({ width: Math.floor(rect.width), height: Math.floor(rect.height) });
});
ro.observe(containerRef.current);
return () => {
ro.disconnect();
chart.remove();
chartRef.current = null;
candleRef.current = null;
volumeRef.current = null;
priceLineRef.current = null;
};
}, []);
// push data when bars change
useEffect(() => {
if (!candleRef.current || !volumeRef.current) return;
if (!bars || bars.length === 0) {
candleRef.current.setData([]);
volumeRef.current.setData([]);
return;
}
candleRef.current.setData(
bars.map((b) => ({
time: b.time,
open: b.open,
high: b.high,
low: b.low,
close: b.close,
})),
);
volumeRef.current.setData(
bars.map((b) => ({
time: b.time,
value: b.volume,
color: b.close >= b.open ? 'rgba(0,255,65,0.35)' : 'rgba(255,180,171,0.35)',
})),
);
}, [bars]);
// apply range filter (or fitContent for All)
useEffect(() => {
const chart = chartRef.current;
if (!chart || !bars || bars.length === 0) return;
const r = range ?? 'All';
if (r === 'All') {
chart.timeScale().fitContent();
return;
}
const n = RANGE_BARS[r];
const fromIdx = Math.max(0, bars.length - n);
chart.timeScale().setVisibleRange({
from: bars[fromIdx].time as Time,
to: bars[bars.length - 1].time as Time,
});
}, [bars, range]);
// current-step marker on the candle series
useEffect(() => {
const series = candleRef.current;
if (!series) return;
if (currentDate) {
const markers: SeriesMarker