Port BR-95 desktop to Next.js components

This commit is contained in:
Alexa Amundson
2025-11-20 19:11:04 -06:00
parent c96df78772
commit 160a4954ec
25 changed files with 2131 additions and 0 deletions

View File

@@ -0,0 +1,412 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
export type WindowState = {
id: string;
position: { x: number; y: number };
size: { width: number; height: number };
isOpen: boolean;
isMaximized: boolean;
zIndex: number;
};
export type LucidiaStats = {
status: string;
activeAgents: number;
totalAgents: number;
memoryJournals: number;
eventBusRate: number;
uptime: number;
};
export type RoadChainStats = {
currentBlock: number;
networkHashrate: string;
activeNodes: number;
yourHashrate: string;
shares: string;
dailyEarnings: string;
};
export type WalletStats = {
balanceRC: number;
balanceUSD: number;
};
export type MinerStats = {
hashRate: string;
sharesAccepted: number;
poolName: string;
};
export type WindowId =
| 'lucidia'
| 'agents'
| 'roadchain'
| 'wallet'
| 'terminal'
| 'roadmail'
| 'social'
| 'blackstream'
| 'roadview'
| 'pi'
| 'miner'
| 'roadcraft';
const API_BASE = '/api/br95';
const WINDOW_PRESETS: Record<WindowId, WindowState> = {
lucidia: { id: 'lucidia', position: { x: 60, y: 90 }, size: { width: 680, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 },
agents: { id: 'agents', position: { x: 120, y: 120 }, size: { width: 760, height: 460 }, isOpen: false, isMaximized: false, zIndex: 10 },
roadchain: { id: 'roadchain', position: { x: 180, y: 80 }, size: { width: 760, height: 440 }, isOpen: false, isMaximized: false, zIndex: 10 },
wallet: { id: 'wallet', position: { x: 220, y: 130 }, size: { width: 520, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 },
terminal: { id: 'terminal', position: { x: 140, y: 180 }, size: { width: 720, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 },
roadmail: { id: 'roadmail', position: { x: 80, y: 80 }, size: { width: 640, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 },
social: { id: 'social', position: { x: 160, y: 90 }, size: { width: 640, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 },
blackstream: { id: 'blackstream', position: { x: 200, y: 100 }, size: { width: 720, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 },
roadview: { id: 'roadview', position: { x: 120, y: 70 }, size: { width: 820, height: 460 }, isOpen: false, isMaximized: false, zIndex: 10 },
pi: { id: 'pi', position: { x: 220, y: 140 }, size: { width: 540, height: 320 }, isOpen: false, isMaximized: false, zIndex: 10 },
miner: { id: 'miner', position: { x: 260, y: 120 }, size: { width: 560, height: 320 }, isOpen: false, isMaximized: false, zIndex: 10 },
roadcraft: { id: 'roadcraft', position: { x: 300, y: 160 }, size: { width: 600, height: 360 }, isOpen: false, isMaximized: false, zIndex: 10 },
};
export function useWindowManager() {
const [windowStates, setWindowStates] = useState<Record<WindowId, WindowState>>(WINDOW_PRESETS);
const [openWindows, setOpenWindows] = useState<WindowId[]>([]);
const [activeWindow, setActiveWindow] = useState<WindowId | null>(null);
const [roadMenuOpen, setRoadMenuOpen] = useState(false);
const [shellReady, setShellReady] = useState(false);
const [clock, setClock] = useState(() => new Date());
const zIndexRef = useRef(10);
const dragRef = useRef<{ id: WindowId; offsetX: number; offsetY: number } | null>(null);
const menuRef = useRef<HTMLDivElement | null>(null);
const menuButtonRef = useRef<HTMLDivElement | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const reconnectRef = useRef<NodeJS.Timeout | null>(null);
const [lucidiaStats, setLucidiaStats] = useState<LucidiaStats>({
status: 'OPERATIONAL',
activeAgents: 1000,
totalAgents: 1000,
memoryJournals: 1000,
eventBusRate: 847,
uptime: 99.95,
});
const [roadchainStats, setRoadchainStats] = useState<RoadChainStats>({
currentBlock: 1247891,
networkHashrate: '847.3 TH/s',
activeNodes: 2847,
yourHashrate: '1.2 GH/s',
shares: '8,423 accepted',
dailyEarnings: '47.23 RC',
});
const [walletStats, setWalletStats] = useState<WalletStats>({
balanceRC: 1247.89,
balanceUSD: 18705,
});
const [minerStats, setMinerStats] = useState<MinerStats>({
hashRate: '1.2 GH/s',
sharesAccepted: 8423,
poolName: 'BRGlobal01',
});
useEffect(() => {
const timer = setTimeout(() => setShellReady(true), 2400);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
const interval = setInterval(() => setClock(new Date()), 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const handleClick = (event: MouseEvent) => {
if (!roadMenuOpen) return;
const target = event.target as Node;
if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) {
return;
}
setRoadMenuOpen(false);
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, [roadMenuOpen]);
const focusWindow = useCallback((id: WindowId) => {
setWindowStates((prev) => {
const current = prev[id];
if (!current) return prev;
const nextZ = ++zIndexRef.current;
return { ...prev, [id]: { ...current, zIndex: nextZ } };
});
setActiveWindow(id);
}, []);
const openWindow = useCallback((id: WindowId) => {
setWindowStates((prev) => {
const current = prev[id];
if (!current) return prev;
const nextZ = ++zIndexRef.current;
return { ...prev, [id]: { ...current, isOpen: true, zIndex: nextZ } };
});
setActiveWindow(id);
setOpenWindows((prev) => (prev.includes(id) ? prev : [...prev, id]));
setRoadMenuOpen(false);
}, []);
const closeWindow = useCallback((id: WindowId) => {
setWindowStates((prev) => {
const current = prev[id];
if (!current) return prev;
return { ...prev, [id]: { ...current, isOpen: false } };
});
setOpenWindows((prev) => prev.filter((win) => win !== id));
setActiveWindow((prev) => (prev === id ? null : prev));
}, []);
const minimizeWindow = useCallback((id: WindowId) => {
setWindowStates((prev) => {
const current = prev[id];
if (!current) return prev;
return { ...prev, [id]: { ...current, isOpen: false } };
});
setActiveWindow((prev) => (prev === id ? null : prev));
}, []);
const maximizeWindow = useCallback((id: WindowId) => {
setWindowStates((prev) => {
const current = prev[id];
if (!current) return prev;
const nextZ = ++zIndexRef.current;
return { ...prev, [id]: { ...current, isMaximized: !current.isMaximized, zIndex: nextZ } };
});
setActiveWindow(id);
}, []);
const startDrag = useCallback((id: WindowId, event: React.MouseEvent) => {
event.preventDefault();
setWindowStates((prev) => {
const current = prev[id];
if (!current || current.isMaximized) return prev;
dragRef.current = {
id,
offsetX: event.clientX - current.position.x,
offsetY: event.clientY - current.position.y,
};
const nextZ = ++zIndexRef.current;
return { ...prev, [id]: { ...current, zIndex: nextZ } };
});
setActiveWindow(id);
}, []);
useEffect(() => {
const handleMove = (event: MouseEvent) => {
if (!dragRef.current) return;
setWindowStates((prev) => {
const current = prev[dragRef.current!.id];
if (!current || current.isMaximized) return prev;
const nextPosition = {
x: event.clientX - dragRef.current!.offsetX,
y: event.clientY - dragRef.current!.offsetY,
};
return { ...prev, [current.id]: { ...current, position: nextPosition } };
});
};
const handleUp = () => {
dragRef.current = null;
};
window.addEventListener('mousemove', handleMove);
window.addEventListener('mouseup', handleUp);
return () => {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('mouseup', handleUp);
};
}, []);
const toggleRoadMenu = useCallback(() => {
setRoadMenuOpen((prev) => !prev);
}, []);
const taskbarToggle = useCallback(
(id: WindowId) => {
if (windowStates[id]?.isOpen && activeWindow === id) {
minimizeWindow(id);
} else {
openWindow(id);
}
},
[activeWindow, minimizeWindow, openWindow, windowStates],
);
const clockText = useMemo(() => clock.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }), [clock]);
const fetchLucidia = useCallback(async () => {
try {
const response = await fetch(`${API_BASE}/lucidia`);
const data = await response.json();
setLucidiaStats((prev) => ({
status: data.status ? String(data.status).toUpperCase() : prev.status,
activeAgents: data.active_agents ?? prev.activeAgents,
totalAgents: data.total_agents ?? prev.totalAgents,
memoryJournals: data.memory_journals ?? prev.memoryJournals,
eventBusRate: data.event_bus_rate ?? prev.eventBusRate,
uptime: data.system_health ?? prev.uptime,
}));
} catch (error) {
console.error('Failed to fetch Lucidia stats:', error);
}
}, []);
const fetchRoadchain = useCallback(async () => {
try {
const response = await fetch(`${API_BASE}/roadchain`);
const data = await response.json();
setRoadchainStats((prev) => ({
currentBlock: data.current_block ?? prev.currentBlock,
networkHashrate: data.network_hashrate ?? prev.networkHashrate,
activeNodes: data.active_nodes ?? prev.activeNodes,
yourHashrate: data.network_hashrate ?? prev.yourHashrate,
shares: prev.shares,
dailyEarnings: prev.dailyEarnings,
}));
} catch (error) {
console.error('Failed to fetch RoadChain stats:', error);
}
}, []);
const fetchWallet = useCallback(async () => {
try {
const response = await fetch(`${API_BASE}/wallet`);
const data = await response.json();
setWalletStats((prev) => ({
balanceRC: data.balance_rc ?? prev.balanceRC,
balanceUSD: data.balance_usd ?? prev.balanceUSD,
}));
} catch (error) {
console.error('Failed to fetch Wallet stats:', error);
}
}, []);
const fetchMiner = useCallback(async () => {
try {
const response = await fetch(`${API_BASE}/miner`);
const data = await response.json();
setMinerStats((prev) => ({
hashRate: data.hash_rate ?? prev.hashRate,
sharesAccepted: data.shares_accepted ?? prev.sharesAccepted,
poolName: data.pool_name ?? prev.poolName,
}));
} catch (error) {
console.error('Failed to fetch Miner stats:', error);
}
}, []);
const connectWebSocket = useCallback(() => {
if (typeof window === 'undefined') return;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}${API_BASE}/ws`;
const socket = new WebSocket(wsUrl);
wsRef.current = socket;
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
switch (message.type) {
case 'miner_update':
setMinerStats((prev) => ({
...prev,
hashRate: message.data?.hash_rate ?? prev.hashRate,
sharesAccepted: message.data?.shares_accepted ?? prev.sharesAccepted,
}));
break;
case 'roadchain_update':
setRoadchainStats((prev) => ({
...prev,
currentBlock: message.data?.current_block ?? prev.currentBlock,
activeNodes: message.data?.active_nodes ?? prev.activeNodes,
}));
break;
case 'wallet_update':
setWalletStats((prev) => ({
...prev,
balanceRC: message.data?.balance_rc ?? prev.balanceRC,
balanceUSD: message.data?.balance_usd ?? prev.balanceUSD,
}));
break;
default:
break;
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
socket.onclose = () => {
if (reconnectRef.current) {
clearTimeout(reconnectRef.current);
}
reconnectRef.current = setTimeout(connectWebSocket, 5000);
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}, []);
useEffect(() => {
if (!shellReady) return;
let lucidiaInterval: NodeJS.Timeout | null = null;
const startApis = () => {
fetchLucidia();
fetchRoadchain();
fetchWallet();
fetchMiner();
connectWebSocket();
lucidiaInterval = setInterval(fetchLucidia, 30000);
};
const timer = setTimeout(startApis, 3000);
return () => {
clearTimeout(timer);
if (lucidiaInterval) {
clearInterval(lucidiaInterval);
}
if (reconnectRef.current) {
clearTimeout(reconnectRef.current);
}
wsRef.current?.close();
};
}, [connectWebSocket, fetchLucidia, fetchMiner, fetchRoadchain, fetchWallet, shellReady]);
return {
windowStates,
openWindows,
activeWindow,
roadMenuOpen,
shellReady,
clockText,
lucidiaStats,
roadchainStats,
walletStats,
minerStats,
menuRef,
menuButtonRef,
openWindow,
closeWindow,
minimizeWindow,
maximizeWindow,
startDrag,
focusWindow,
toggleRoadMenu,
taskbarToggle,
};
}