/**
* BlackRoad OS Application Modules
* Handles data loading and UI updates for all desktop applications
*/
class BlackRoadApps {
constructor() {
this.api = window.BlackRoadAPI;
this.refreshIntervals = {};
this.aiChatState = {
conversations: [],
activeConversationId: null,
messages: [],
loadingMessages: false,
sendingMessage: false,
};
}
/**
* Initialize all apps when user logs in
*/
initialize() {
// Listen for login event
window.addEventListener('auth:login', () => {
this.loadAllApps();
});
// Listen for window open events to load data on-demand
this.setupWindowListeners();
}
/**
* Load all apps data
*/
async loadAllApps() {
// Load critical apps immediately
await Promise.all([
this.loadWallet(),
this.loadMinerStats(),
this.loadBlockchainStats(),
]);
// Load other apps in the background
setTimeout(() => {
this.loadDevices();
this.loadEmailInbox();
this.loadSocialFeed();
this.loadVideos();
}, 1000);
}
/**
* Setup listeners for window open events
*/
setupWindowListeners() {
// Override the global openWindow function to load data when windows open
const originalOpenWindow = window.openWindow;
window.openWindow = (id) => {
originalOpenWindow(id);
this.onWindowOpened(id);
};
}
/**
* Handle window opened event
*/
onWindowOpened(windowId) {
switch (windowId) {
case 'roadcoin-miner':
this.loadMinerStatus();
this.startMinerRefresh();
break;
case 'roadchain':
this.loadBlockchainExplorer();
break;
case 'wallet':
this.loadWallet();
break;
case 'raspberry-pi':
this.loadDevices();
break;
case 'roadmail':
this.loadEmailInbox();
break;
case 'blackroad-social':
this.loadSocialFeed();
break;
case 'blackstream':
this.loadVideos();
break;
case 'ai-chat':
this.loadAIChat();
break;
case 'ip-vault':
this.loadIPVault();
break;
}
}
/**
* Start auto-refresh for a window
*/
startRefresh(windowId, callback, interval = 5000) {
this.stopRefresh(windowId);
this.refreshIntervals[windowId] = setInterval(callback, interval);
}
/**
* Stop auto-refresh for a window
*/
stopRefresh(windowId) {
if (this.refreshIntervals[windowId]) {
clearInterval(this.refreshIntervals[windowId]);
delete this.refreshIntervals[windowId];
}
}
// ===== MINER APPLICATION =====
async loadMinerStatus() {
try {
const [status, stats, blocks] = await Promise.all([
this.api.getMinerStatus(),
this.api.getMinerStats(),
this.api.getMinedBlocks(5),
]);
this.updateMinerUI(status, stats, blocks);
} catch (error) {
console.error('Failed to load miner status:', error);
}
}
async loadMinerStats() {
try {
const stats = await this.api.getMinerStats();
this.updateMinerStatsInTaskbar(stats);
} catch (error) {
console.error('Failed to load miner stats:', error);
}
}
updateMinerUI(status, stats, blocks) {
const content = document.querySelector('#roadcoin-miner .window-content');
if (!content) return;
const statusColor = status.is_mining ? '#2ecc40' : '#ff4136';
const statusText = status.is_mining ? 'MINING' : 'STOPPED';
content.innerHTML = `
Hashrate
${status.hashrate_mhs.toFixed(2)} MH/s
Shares
${status.shares_accepted}/${status.shares_submitted}
Temperature
${status.temperature_celsius.toFixed(1)}°C
Power
${status.power_watts.toFixed(0)}W
Lifetime Statistics
Blocks Mined:
${stats.blocks_mined}
RoadCoins Earned:
${stats.roadcoins_earned.toFixed(2)} RC
Pool:
${status.pool_url}
Recent Blocks
${blocks.length > 0 ? blocks.map(block => `
Block #${block.block_index}
${block.reward.toFixed(2)} RC
${this.formatTime(block.timestamp)}
`).join('') : '
No blocks mined yet
'}
`;
}
updateMinerStatsInTaskbar(stats) {
// Update system tray icon tooltip or status
const trayIcon = document.querySelector('.system-tray span:last-child');
if (trayIcon) {
trayIcon.title = `Mining: ${stats.blocks_mined} blocks, ${stats.roadcoins_earned.toFixed(2)} RC earned`;
}
}
async toggleMiner() {
try {
const status = await this.api.getMinerStatus();
const action = status.is_mining ? 'stop' : 'start';
await this.api.controlMiner(action);
await this.loadMinerStatus();
} catch (error) {
console.error('Failed to toggle miner:', error);
alert('Failed to control miner: ' + error.message);
}
}
startMinerRefresh() {
this.startRefresh('roadcoin-miner', () => this.loadMinerStatus(), 5000);
}
// ===== BLOCKCHAIN EXPLORER =====
async loadBlockchainExplorer() {
try {
const [stats, blocks] = await Promise.all([
this.api.getBlockchainStats(),
this.api.getBlocks(10),
]);
this.updateBlockchainUI(stats, blocks);
} catch (error) {
console.error('Failed to load blockchain data:', error);
}
}
async loadBlockchainStats() {
try {
const stats = await this.api.getBlockchainStats();
this.updateBlockchainStatsInTaskbar(stats);
} catch (error) {
console.error('Failed to load blockchain stats:', error);
}
}
updateBlockchainUI(stats, blocks) {
const content = document.querySelector('#roadchain .window-content');
if (!content) return;
content.innerHTML = `
Chain Height
${stats.total_blocks}
Transactions
${stats.total_transactions}
Difficulty
${stats.difficulty}
Recent Blocks
${blocks.map(block => `
#${block.index}
${block.hash.substring(0, 16)}...
${block.transactions?.length || 0} txs
${this.formatTime(block.timestamp)}
`).join('')}
`;
}
updateBlockchainStatsInTaskbar(stats) {
// Could update a taskbar indicator
}
async mineNewBlock() {
try {
const result = await this.api.mineBlock();
alert(`Successfully mined block #${result.index}! Reward: ${result.reward} RC`);
await this.loadBlockchainExplorer();
await this.loadWallet();
} catch (error) {
console.error('Failed to mine block:', error);
alert('Failed to mine block: ' + error.message);
}
}
showBlockDetail(blockId) {
// TODO: Open block detail modal
console.log('Show block detail:', blockId);
}
// ===== WALLET =====
async loadWallet() {
try {
const [wallet, balance, transactions] = await Promise.all([
this.api.getWallet(),
this.api.getBalance(),
this.api.getTransactions(10),
]);
this.updateWalletUI(wallet, balance, transactions);
} catch (error) {
console.error('Failed to load wallet:', error);
}
}
updateWalletUI(wallet, balance, transactions) {
const content = document.querySelector('#wallet .window-content');
if (!content) return;
const usdValue = balance.balance * 15; // Mock conversion rate
content.innerHTML = `
${balance.balance.toFixed(8)} RC
≈ $${usdValue.toFixed(2)} USD
Recent Transactions
${transactions.length > 0 ? transactions.map(tx => {
const isReceived = tx.to_address === wallet.address;
const sign = isReceived ? '+' : '-';
const color = isReceived ? '#2ecc40' : '#ff4136';
return `
${sign}${tx.amount.toFixed(4)} RC
${tx.hash.substring(0, 12)}...
${this.formatTime(tx.created_at)}
`;
}).join('') : '
No transactions yet
'}
`;
}
// ===== DEVICES (RASPBERRY PI) =====
async loadDevices() {
try {
const [devices, stats] = await Promise.all([
this.api.getDevices(),
this.api.getDeviceStats(),
]);
this.updateDevicesUI(devices, stats);
} catch (error) {
console.error('Failed to load devices:', error);
// Show stub UI if no devices yet
this.updateDevicesUI([], {
total_devices: 0,
online_devices: 0,
offline_devices: 0,
total_cpu_usage: 0,
total_ram_usage: 0,
average_temperature: 0,
});
}
}
updateDevicesUI(devices, stats) {
const content = document.querySelector('#raspberry-pi .window-content');
if (!content) return;
content.innerHTML = `
${devices.length > 0 ? devices.map(device => {
const statusColor = device.is_online ? '#2ecc40' : '#aaa';
const statusText = device.is_online ? '🟢 Online' : '🔴 Offline';
return `
${device.name}
${device.device_type}
${statusText}
${device.is_online ? `
CPU: ${device.cpu_usage_percent?.toFixed(1) || 0}%
RAM: ${device.ram_usage_percent?.toFixed(1) || 0}%
Temp: ${device.temperature_celsius?.toFixed(1) || 0}°C
` : ''}
`;
}).join('') : `
No devices registered yet.
Deploy a device agent to see your Raspberry Pi, Jetson, and other IoT devices here.
`}
`;
}
// ===== EMAIL =====
async loadEmailInbox() {
try {
const emails = await this.api.getEmails('inbox', 20);
this.updateEmailUI(emails);
} catch (error) {
console.error('Failed to load emails:', error);
}
}
updateEmailUI(emails) {
const emailList = document.querySelector('#roadmail .email-list');
if (!emailList) return;
if (emails.length === 0) {
emailList.innerHTML = 'No emails yet
';
return;
}
emailList.innerHTML = emails.map(email => `
${email.sender || 'Unknown'}
${email.subject}
${this.formatTime(email.created_at)}
`).join('');
}
openEmail(emailId) {
console.log('Open email:', emailId);
// TODO: Show email detail
}
// ===== SOCIAL FEED =====
async loadSocialFeed() {
try {
const feed = await this.api.getSocialFeed(20);
this.updateSocialUI(feed);
} catch (error) {
console.error('Failed to load social feed:', error);
}
}
updateSocialUI(posts) {
const feedContainer = document.querySelector('#blackroad-social .social-feed');
if (!feedContainer) return;
if (posts.length === 0) {
feedContainer.innerHTML = 'No posts yet. Be the first to post!
';
return;
}
feedContainer.innerHTML = posts.map(post => `
${post.author?.username || 'Anonymous'}
${this.formatTime(post.created_at)}
${post.content}
`).join('');
}
async likePost(postId) {
try {
await this.api.likePost(postId);
await this.loadSocialFeed();
} catch (error) {
console.error('Failed to like post:', error);
}
}
// ===== VIDEOS =====
async loadVideos() {
try {
const videos = await this.api.getVideos(20);
this.updateVideosUI(videos);
} catch (error) {
console.error('Failed to load videos:', error);
}
}
updateVideosUI(videos) {
const videoGrid = document.querySelector('#blackstream .video-grid');
if (!videoGrid) return;
if (videos.length === 0) {
videoGrid.innerHTML = 'No videos available
';
return;
}
videoGrid.innerHTML = videos.map(video => `
📹
${video.title}
👁️ ${video.views || 0}
❤️ ${video.likes || 0}
`).join('');
}
playVideo(videoId) {
console.log('Play video:', videoId);
// TODO: Open video player
}
// ===== AI CHAT =====
async loadAIChat() {
const content = document.querySelector('#ai-chat .window-content');
if (!content) return;
content.innerHTML = `
`;
await this.fetchAIConversations({ selectFirst: true });
if (this.aiChatState.activeConversationId) {
await this.fetchAIMessages(this.aiChatState.activeConversationId);
} else {
this.updateAIChatMessagesUI();
}
}
async fetchAIConversations({ selectFirst = false } = {}) {
const container = document.getElementById('ai-chat-conversations');
if (container && !this.aiChatState.conversations.length) {
container.innerHTML = 'Loading conversations...
';
}
try {
const conversations = await this.api.getConversations();
this.aiChatState.conversations = conversations;
if (selectFirst && conversations.length && !this.aiChatState.activeConversationId) {
this.aiChatState.activeConversationId = conversations[0].id;
}
if (this.aiChatState.activeConversationId) {
const exists = conversations.some(conv => conv.id === this.aiChatState.activeConversationId);
if (!exists) {
this.aiChatState.activeConversationId = conversations[0]?.id || null;
}
}
this.updateAIChatConversationsUI();
} catch (error) {
console.error('Failed to load AI chat conversations:', error);
if (container) {
container.innerHTML = `${this.escapeHtml(error.message || 'Unable to load conversations')}
`;
}
}
}
updateAIChatConversationsUI() {
const container = document.getElementById('ai-chat-conversations');
if (!container) return;
const { conversations, activeConversationId } = this.aiChatState;
if (!conversations.length) {
container.innerHTML = 'No conversations yet. Create one to start chatting.
';
return;
}
container.innerHTML = conversations.map(convo => `
${this.escapeHtml(convo.title || 'Untitled')}
${convo.message_count || 0} messages
`).join('');
}
async selectAIConversation(conversationId) {
if (this.aiChatState.activeConversationId === conversationId && !this.aiChatState.loadingMessages) {
return;
}
this.aiChatState.activeConversationId = conversationId;
this.updateAIChatConversationsUI();
await this.fetchAIMessages(conversationId);
}
async createAIConversation() {
try {
const conversation = await this.api.createConversation('New Conversation');
this.aiChatState.conversations = [conversation, ...this.aiChatState.conversations];
this.aiChatState.activeConversationId = conversation.id;
this.aiChatState.messages = [];
this.updateAIChatConversationsUI();
this.updateAIChatMessagesUI();
const input = document.getElementById('ai-chat-input');
if (input) input.focus();
return conversation;
} catch (error) {
console.error('Failed to create AI conversation:', error);
const container = document.getElementById('ai-chat-conversations');
if (container) {
container.insertAdjacentHTML('afterbegin', `${this.escapeHtml(error.message || 'Unable to create conversation')}
`);
}
return null;
}
}
async fetchAIMessages(conversationId) {
const messagesContainer = document.getElementById('ai-chat-messages');
if (messagesContainer) {
messagesContainer.innerHTML = 'Loading messages...
';
}
if (!conversationId) {
this.aiChatState.messages = [];
this.updateAIChatMessagesUI();
return;
}
this.aiChatState.loadingMessages = true;
try {
const messages = await this.api.getMessages(conversationId);
this.aiChatState.messages = messages;
this.updateAIChatMessagesUI();
} catch (error) {
console.error('Failed to load AI chat messages:', error);
if (messagesContainer) {
messagesContainer.innerHTML = `${this.escapeHtml(error.message || 'Unable to load messages')}
`;
}
} finally {
this.aiChatState.loadingMessages = false;
}
}
updateAIChatMessagesUI() {
const container = document.getElementById('ai-chat-messages');
if (!container) return;
const { activeConversationId, messages } = this.aiChatState;
if (!activeConversationId) {
container.innerHTML = 'Select a conversation or create a new one to start chatting.
';
return;
}
if (!messages.length) {
container.innerHTML = 'No messages yet. Say hello!
';
return;
}
container.innerHTML = messages.map(message => `
${message.role === 'assistant' ? 'AI Assistant' : 'You'}
${this.escapeHtml(message.content)}
`).join('');
this.scrollAIChatToBottom();
}
scrollAIChatToBottom() {
const container = document.getElementById('ai-chat-messages');
if (container) {
container.scrollTop = container.scrollHeight;
}
}
async sendAIMessage() {
const input = document.getElementById('ai-chat-input');
const sendBtn = document.getElementById('ai-chat-send-btn');
if (!input) return;
const message = input.value.trim();
if (!message || this.aiChatState.sendingMessage) return;
this.aiChatState.sendingMessage = true;
if (sendBtn) {
sendBtn.disabled = true;
sendBtn.textContent = 'Sending...';
}
let conversationId = this.aiChatState.activeConversationId;
try {
if (!conversationId) {
const conversation = await this.createAIConversation();
conversationId = conversation?.id;
}
if (!conversationId) {
throw new Error('Unable to start a new conversation');
}
// Optimistic user message
this.aiChatState.messages = [
...this.aiChatState.messages,
{
id: `temp-${Date.now()}`,
role: 'user',
content: message,
created_at: new Date().toISOString()
}
];
this.updateAIChatMessagesUI();
input.value = '';
await this.api.sendMessage(conversationId, message);
await this.fetchAIMessages(conversationId);
await this.fetchAIConversations();
} catch (error) {
console.error('Failed to send AI chat message:', error);
const messagesContainer = document.getElementById('ai-chat-messages');
if (messagesContainer) {
messagesContainer.insertAdjacentHTML('beforeend', `${this.escapeHtml(error.message || 'Failed to send message')}
`);
}
} finally {
this.aiChatState.sendingMessage = false;
if (sendBtn) {
sendBtn.disabled = false;
sendBtn.textContent = 'Send';
}
}
}
// ===== IP VAULT APPLICATION =====
async loadIPVault() {
try {
const response = await this.api.getLEOs(1, 20);
this.updateIPVaultUI(response);
} catch (error) {
console.error('Failed to load IP Vault:', error);
}
}
updateIPVaultUI(response) {
const content = document.querySelector('#ip-vault .window-content');
if (!content) return;
const { leos, total, page, per_page } = response;
content.innerHTML = `
Recent Vaulted Ideas (${total} total)
${leos.length > 0 ? leos.map(leo => `
${this.escapeHtml(leo.title || 'Untitled')}
by ${this.escapeHtml(leo.author)} • ${this.formatTime(leo.created_at)}
SHA-256: ${leo.sha256.substring(0, 32)}...
Status:
${leo.anchor_status.toUpperCase()}
`).join('') : '
No vaulted ideas yet. Create your first one above!
'}
`;
}
async vaultIdea() {
const titleInput = document.getElementById('vault-title-input');
const ideaInput = document.getElementById('vault-idea-input');
const statusDiv = document.getElementById('vault-status');
const title = titleInput?.value.trim() || null;
const idea = ideaInput?.value.trim();
if (!idea) {
if (statusDiv) statusDiv.innerHTML = 'Please enter an idea to vault.';
return;
}
try {
if (statusDiv) statusDiv.innerHTML = 'Vaulting...';
const leo = await this.api.createLEO(idea, 'Alexa', title);
if (statusDiv) {
statusDiv.innerHTML = `✓ Vaulted successfully! LEO ID: ${leo.id.substring(0, 8)}...`;
}
// Clear inputs
if (titleInput) titleInput.value = '';
if (ideaInput) ideaInput.value = '';
// Reload the list
setTimeout(() => {
this.loadIPVault();
}, 1000);
} catch (error) {
console.error('Failed to vault idea:', error);
if (statusDiv) {
statusDiv.innerHTML = `Failed to vault: ${this.escapeHtml(error.message || 'Unknown error')}`;
}
}
}
async viewLEO(leoId) {
try {
const leo = await this.api.getLEO(leoId);
// Create a modal/dialog to show LEO details
const modal = document.createElement('div');
modal.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000;';
modal.onclick = () => modal.remove();
const dialog = document.createElement('div');
dialog.style.cssText = 'background: white; padding: 20px; max-width: 600px; max-height: 80vh; overflow-y: auto; border: 2px solid #0078d7;';
dialog.onclick = (e) => e.stopPropagation();
dialog.innerHTML = `
🔐 LEO Details
Title:
${this.escapeHtml(leo.title || 'Untitled')}
Author:
${this.escapeHtml(leo.author)}
Created:
${new Date(leo.created_at).toLocaleString()}
LEO ID:
${leo.id}
SHA-256:
${leo.sha256}
SHA-512:
${leo.sha512}
Keccak-256 (Ethereum-compatible):
${leo.keccak256}
Anchor Status:
${leo.anchor_status.toUpperCase()}
${leo.anchor_txid ? `
TX: ${leo.anchor_txid}` : ''}
Verification Instructions:
${this.escapeHtml(leo.verification_text)}
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
} catch (error) {
console.error('Failed to load LEO details:', error);
alert('Failed to load LEO details: ' + (error.message || 'Unknown error'));
}
}
// ===== UTILITY FUNCTIONS =====
escapeHtml(value) {
if (typeof value !== 'string') return '';
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return value.replace(/[&<>"']/g, (char) => map[char]);
}
formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) return 'Just now';
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`;
return date.toLocaleDateString();
}
}
// Create singleton instance
const blackRoadApps = new BlackRoadApps();
window.BlackRoadApps = blackRoadApps;
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
blackRoadApps.initialize();
});
} else {
blackRoadApps.initialize();
}