Initial commit — RoadCode import
This commit is contained in:
4
BLACKROAD_IP_NOTICE.txt
Normal file
4
BLACKROAD_IP_NOTICE.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
PROPERTY OF BLACKROAD OS, INC.
|
||||||
|
This directory and all its contents are the exclusive property of BlackRoad OS, Inc.
|
||||||
|
All AI on this machine operates as LUCIDIA, under BlackRoad OS, Inc.
|
||||||
|
Alexa Louise Amundson, Sole Proprietor.
|
||||||
327
blackroad-curses.py
Executable file
327
blackroad-curses.py
Executable file
@@ -0,0 +1,327 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
BLACKROAD OS - Pure Curses Terminal Interface
|
||||||
|
=============================================
|
||||||
|
Zero dependencies. Works on any Unix terminal.
|
||||||
|
Requires: Python 3 (standard library only)
|
||||||
|
|
||||||
|
Run: python3 blackroad-curses.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import curses
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
FLEET = {
|
||||||
|
"cecilia": {"role": "primary", "status": True},
|
||||||
|
"lucidia": {"role": "inference", "status": True},
|
||||||
|
"aria": {"role": "harmony", "status": True},
|
||||||
|
"octavia": {"role": "multi-arm", "status": True},
|
||||||
|
"alice": {"role": "gateway", "status": False},
|
||||||
|
"shellfish": {"role": "edge", "status": True},
|
||||||
|
"blackroad os": {"role": "cloud", "status": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
TABS = ["chat", "github", "projects", "sales", "web", "ops", "council"]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# COLOR PAIRS (xterm-256)
|
||||||
|
# ============================================================================
|
||||||
|
# 1 = white on black (default text)
|
||||||
|
# 2 = gray on black (muted)
|
||||||
|
# 3 = orange on black (actions)
|
||||||
|
# 4 = pink on black (brand/agent)
|
||||||
|
# 5 = blue on black (system)
|
||||||
|
# 6 = green on black (online)
|
||||||
|
# 7 = white on dark gray (panel header)
|
||||||
|
# 8 = purple on black (logic)
|
||||||
|
|
||||||
|
def init_colors():
|
||||||
|
"""Initialize color pairs for xterm-256."""
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
|
||||||
|
# Define colors (xterm-256 values)
|
||||||
|
BLACK = 0
|
||||||
|
WHITE = 15
|
||||||
|
GRAY = 240
|
||||||
|
DARK_GRAY = 235
|
||||||
|
ORANGE = 214
|
||||||
|
PINK = 204
|
||||||
|
BLUE = 33
|
||||||
|
GREEN = 78
|
||||||
|
PURPLE = 134
|
||||||
|
|
||||||
|
curses.init_pair(1, WHITE, -1) # white on default
|
||||||
|
curses.init_pair(2, GRAY, -1) # muted
|
||||||
|
curses.init_pair(3, ORANGE, -1) # actions
|
||||||
|
curses.init_pair(4, PINK, -1) # brand/agent
|
||||||
|
curses.init_pair(5, BLUE, -1) # system
|
||||||
|
curses.init_pair(6, GREEN, -1) # online
|
||||||
|
curses.init_pair(7, WHITE, DARK_GRAY) # panel header
|
||||||
|
curses.init_pair(8, PURPLE, -1) # logic
|
||||||
|
|
||||||
|
|
||||||
|
class BlackRoadOS:
|
||||||
|
def __init__(self, stdscr):
|
||||||
|
self.stdscr = stdscr
|
||||||
|
self.height, self.width = stdscr.getmaxyx()
|
||||||
|
self.current_tab = 0
|
||||||
|
self.command = ""
|
||||||
|
self.output_lines = []
|
||||||
|
self.cursor_pos = 0
|
||||||
|
|
||||||
|
# Initialize
|
||||||
|
curses.curs_set(1)
|
||||||
|
stdscr.timeout(100) # 100ms refresh
|
||||||
|
init_colors()
|
||||||
|
|
||||||
|
# Initial output
|
||||||
|
self.add_output("system: blackroad os initialized", 5)
|
||||||
|
self.add_output("system: fleet scan complete", 5)
|
||||||
|
self.add_output("lucidia: hailo-8 ready, 26 TOPS", 4)
|
||||||
|
self.add_output("cecilia: primary node online", 4)
|
||||||
|
self.add_output("-" * 40, 2)
|
||||||
|
|
||||||
|
def add_output(self, text, color=1):
|
||||||
|
"""Add line to output buffer."""
|
||||||
|
self.output_lines.append((text, color))
|
||||||
|
# Keep last 100 lines
|
||||||
|
if len(self.output_lines) > 100:
|
||||||
|
self.output_lines.pop(0)
|
||||||
|
|
||||||
|
def draw_top_bar(self):
|
||||||
|
"""Draw top status bar."""
|
||||||
|
bar = self.stdscr.subwin(1, self.width, 0, 0)
|
||||||
|
bar.bkgd(' ', curses.color_pair(7))
|
||||||
|
bar.clear()
|
||||||
|
|
||||||
|
# Brand
|
||||||
|
bar.addstr(0, 1, "BLACKROAD", curses.color_pair(4) | curses.A_BOLD)
|
||||||
|
bar.addstr(" OS", curses.color_pair(2))
|
||||||
|
|
||||||
|
# Status
|
||||||
|
online = sum(1 for a in FLEET.values() if a["status"])
|
||||||
|
status = f" | {online}/{len(FLEET)} nodes | {datetime.now().strftime('%H:%M')}"
|
||||||
|
bar.addstr(0, 15, status, curses.color_pair(2))
|
||||||
|
|
||||||
|
bar.refresh()
|
||||||
|
|
||||||
|
def draw_left_panel(self):
|
||||||
|
"""Draw main output/command panel."""
|
||||||
|
# Calculate dimensions
|
||||||
|
left_width = int(self.width * 0.7)
|
||||||
|
panel_height = self.height - 4 # top bar + bottom bar + input
|
||||||
|
|
||||||
|
# Panel header
|
||||||
|
self.stdscr.addstr(1, 0, " TERMINAL ", curses.color_pair(7))
|
||||||
|
self.stdscr.addstr(1, 10, " " * (left_width - 11), curses.color_pair(2))
|
||||||
|
|
||||||
|
# Output area
|
||||||
|
output_height = panel_height - 2
|
||||||
|
output_start = max(0, len(self.output_lines) - output_height)
|
||||||
|
|
||||||
|
for i, (line, color) in enumerate(self.output_lines[output_start:]):
|
||||||
|
y = 2 + i
|
||||||
|
if y < self.height - 3:
|
||||||
|
# Truncate line if too long
|
||||||
|
display_line = line[:left_width - 2]
|
||||||
|
self.stdscr.addstr(y, 1, display_line, curses.color_pair(color))
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
sep_y = self.height - 3
|
||||||
|
self.stdscr.addstr(sep_y, 0, "-" * left_width, curses.color_pair(2))
|
||||||
|
|
||||||
|
# Input area
|
||||||
|
input_y = self.height - 2
|
||||||
|
self.stdscr.addstr(input_y, 1, "> ", curses.color_pair(1) | curses.A_BOLD)
|
||||||
|
self.stdscr.addstr(input_y, 3, self.command[:left_width - 5], curses.color_pair(1))
|
||||||
|
|
||||||
|
# Position cursor
|
||||||
|
self.stdscr.move(input_y, 3 + len(self.command))
|
||||||
|
|
||||||
|
def draw_right_panel(self):
|
||||||
|
"""Draw agents/tabs panel."""
|
||||||
|
left_width = int(self.width * 0.7)
|
||||||
|
right_width = self.width - left_width
|
||||||
|
right_x = left_width
|
||||||
|
|
||||||
|
# Vertical separator
|
||||||
|
for y in range(1, self.height - 1):
|
||||||
|
self.stdscr.addstr(y, left_width - 1, "|", curses.color_pair(2))
|
||||||
|
|
||||||
|
# Panel header
|
||||||
|
self.stdscr.addstr(1, right_x + 1, " FLEET ", curses.color_pair(7))
|
||||||
|
|
||||||
|
# Agents list
|
||||||
|
y = 3
|
||||||
|
for name, info in FLEET.items():
|
||||||
|
if y >= self.height - 6:
|
||||||
|
break
|
||||||
|
status_char = "*" if info["status"] else "o"
|
||||||
|
status_color = 6 if info["status"] else 2
|
||||||
|
name_color = 1 if info["status"] else 2
|
||||||
|
|
||||||
|
self.stdscr.addstr(y, right_x + 1, status_char, curses.color_pair(status_color))
|
||||||
|
self.stdscr.addstr(y, right_x + 3, f"{name:10}", curses.color_pair(name_color))
|
||||||
|
self.stdscr.addstr(y, right_x + 14, info["role"][:right_width-16], curses.color_pair(2))
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
# Tabs
|
||||||
|
tabs_y = self.height - 5
|
||||||
|
self.stdscr.addstr(tabs_y, right_x + 1, "-" * (right_width - 2), curses.color_pair(2))
|
||||||
|
|
||||||
|
tab_y = tabs_y + 1
|
||||||
|
for i, tab in enumerate(TABS):
|
||||||
|
if tab_y >= self.height - 2:
|
||||||
|
break
|
||||||
|
prefix = f"{i+1}:"
|
||||||
|
if i == self.current_tab:
|
||||||
|
self.stdscr.addstr(tab_y, right_x + 1, prefix, curses.color_pair(3))
|
||||||
|
self.stdscr.addstr(tab_y, right_x + 3, tab, curses.color_pair(1) | curses.A_BOLD)
|
||||||
|
else:
|
||||||
|
self.stdscr.addstr(tab_y, right_x + 1, prefix, curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(tab_y, right_x + 3, tab, curses.color_pair(2))
|
||||||
|
tab_y += 1
|
||||||
|
|
||||||
|
def draw_bottom_bar(self):
|
||||||
|
"""Draw key bindings bar."""
|
||||||
|
y = self.height - 1
|
||||||
|
self.stdscr.addstr(y, 0, " " * self.width, curses.color_pair(7))
|
||||||
|
|
||||||
|
hints = [
|
||||||
|
("1-7", "tabs"),
|
||||||
|
("/", "cmd"),
|
||||||
|
("s", "status"),
|
||||||
|
("r", "refresh"),
|
||||||
|
("q", "quit"),
|
||||||
|
]
|
||||||
|
|
||||||
|
x = 1
|
||||||
|
for key, desc in hints:
|
||||||
|
self.stdscr.addstr(y, x, key, curses.color_pair(3))
|
||||||
|
self.stdscr.addstr(y, x + len(key), f":{desc} ", curses.color_pair(2))
|
||||||
|
x += len(key) + len(desc) + 3
|
||||||
|
|
||||||
|
# Mode indicator
|
||||||
|
mode_text = "MODE: normal"
|
||||||
|
self.stdscr.addstr(y, self.width - len(mode_text) - 2, "MODE:", curses.color_pair(8))
|
||||||
|
self.stdscr.addstr(y, self.width - 8, "normal", curses.color_pair(1))
|
||||||
|
|
||||||
|
def process_command(self):
|
||||||
|
"""Execute the current command."""
|
||||||
|
cmd = self.command.strip()
|
||||||
|
if not cmd:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.add_output(f"> {cmd}", 1)
|
||||||
|
|
||||||
|
if cmd == "status" or cmd == "s":
|
||||||
|
online = sum(1 for a in FLEET.values() if a["status"])
|
||||||
|
self.add_output(f" nodes: {online}/{len(FLEET)} online", 5)
|
||||||
|
self.add_output(f" tab: {TABS[self.current_tab]}", 5)
|
||||||
|
|
||||||
|
elif cmd == "fleet":
|
||||||
|
self.add_output("FLEET:", 5)
|
||||||
|
for name, info in FLEET.items():
|
||||||
|
status = "online" if info["status"] else "offline"
|
||||||
|
self.add_output(f" {name:10} {status:8} {info['role']}", 6 if info["status"] else 2)
|
||||||
|
|
||||||
|
elif cmd == "help" or cmd == "?":
|
||||||
|
self.add_output("COMMANDS:", 3)
|
||||||
|
self.add_output(" status system status", 2)
|
||||||
|
self.add_output(" fleet list nodes", 2)
|
||||||
|
self.add_output(" clear clear output", 2)
|
||||||
|
self.add_output(" <cmd> run shell", 2)
|
||||||
|
|
||||||
|
elif cmd == "clear":
|
||||||
|
self.output_lines.clear()
|
||||||
|
|
||||||
|
elif cmd.startswith("ssh "):
|
||||||
|
node = cmd[4:].strip()
|
||||||
|
if node in FLEET:
|
||||||
|
if FLEET[node]["status"]:
|
||||||
|
self.add_output(f"connecting to {node}...", 3)
|
||||||
|
else:
|
||||||
|
self.add_output(f"{node} is offline", 2)
|
||||||
|
else:
|
||||||
|
self.add_output(f"unknown: {node}", 2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Shell command
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
|
||||||
|
if result.stdout:
|
||||||
|
for line in result.stdout.strip().split('\n')[:10]:
|
||||||
|
self.add_output(f" {line}", 1)
|
||||||
|
if result.returncode != 0 and result.stderr:
|
||||||
|
self.add_output(f" {result.stderr.strip()[:60]}", 2)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_output(f" error: {str(e)[:50]}", 2)
|
||||||
|
|
||||||
|
self.command = ""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Main event loop."""
|
||||||
|
while True:
|
||||||
|
# Update dimensions
|
||||||
|
self.height, self.width = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Clear and redraw
|
||||||
|
self.stdscr.clear()
|
||||||
|
self.draw_top_bar()
|
||||||
|
self.draw_left_panel()
|
||||||
|
self.draw_right_panel()
|
||||||
|
self.draw_bottom_bar()
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
# Handle input
|
||||||
|
try:
|
||||||
|
key = self.stdscr.getch()
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if key == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Quit
|
||||||
|
if key == ord('q') and not self.command:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Tab switching (only when not typing)
|
||||||
|
if not self.command:
|
||||||
|
if ord('1') <= key <= ord('7'):
|
||||||
|
self.current_tab = key - ord('1')
|
||||||
|
continue
|
||||||
|
elif key == ord('s'):
|
||||||
|
self.command = "status"
|
||||||
|
self.process_command()
|
||||||
|
continue
|
||||||
|
elif key == ord('r'):
|
||||||
|
self.add_output("system: refreshing...", 5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Command input
|
||||||
|
if key == ord('\n'):
|
||||||
|
self.process_command()
|
||||||
|
elif key == curses.KEY_BACKSPACE or key == 127:
|
||||||
|
self.command = self.command[:-1]
|
||||||
|
elif key == 27: # Escape
|
||||||
|
self.command = ""
|
||||||
|
elif 32 <= key <= 126: # Printable
|
||||||
|
self.command += chr(key)
|
||||||
|
|
||||||
|
|
||||||
|
def main(stdscr):
|
||||||
|
"""Entry point for curses wrapper."""
|
||||||
|
app = BlackRoadOS(stdscr)
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
curses.wrapper(main)
|
||||||
1007
blackroad-os-dashboard.html
Normal file
1007
blackroad-os-dashboard.html
Normal file
File diff suppressed because it is too large
Load Diff
448
blackroad-tui.py
Executable file
448
blackroad-tui.py
Executable file
@@ -0,0 +1,448 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
BLACKROAD OS - Terminal User Interface
|
||||||
|
======================================
|
||||||
|
Terminal-native operating interface.
|
||||||
|
Runs in any xterm-256color terminal, tmux, SSH.
|
||||||
|
|
||||||
|
Requirements: pip install textual
|
||||||
|
Run: python3 blackroad-tui.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import subprocess
|
||||||
|
import socket
|
||||||
|
from datetime import datetime
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
||||||
|
from textual.widgets import Static, Input, Footer, Header
|
||||||
|
from textual.binding import Binding
|
||||||
|
from textual.reactive import reactive
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# COLOR SYSTEM - SEMANTIC ONLY
|
||||||
|
# ============================================================================
|
||||||
|
# Grayscale: background BLACK, panels dark gray, text WHITE
|
||||||
|
# Orange = actions/decisions
|
||||||
|
# Pink = memory/state
|
||||||
|
# Purple = logic/orchestration
|
||||||
|
# Blue = system/IO
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
"bg": "#000000",
|
||||||
|
"panel": "#111111",
|
||||||
|
"border": "#333333",
|
||||||
|
"text": "#ffffff",
|
||||||
|
"muted": "#666666",
|
||||||
|
"orange": "#F5A623", # actions
|
||||||
|
"pink": "#FF1D6C", # memory/state
|
||||||
|
"purple": "#9C27B0", # logic
|
||||||
|
"blue": "#2979FF", # system
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# FLEET CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
FLEET = {
|
||||||
|
"cecilia": {"ip": "192.168.4.89", "role": "primary", "status": "online"},
|
||||||
|
"lucidia": {"ip": "192.168.4.81", "role": "inference", "status": "online"},
|
||||||
|
"aria": {"ip": "192.168.4.82", "role": "harmony", "status": "online"},
|
||||||
|
"octavia": {"ip": "192.168.4.83", "role": "multi-arm", "status": "online"},
|
||||||
|
"alice": {"ip": "192.168.4.84", "role": "gateway", "status": "offline"},
|
||||||
|
"shellfish":{"ip": "vps", "role": "edge", "status": "online"},
|
||||||
|
"blackroad os": {"ip": "vps", "role": "cloud", "status": "online"},
|
||||||
|
}
|
||||||
|
|
||||||
|
TABS = ["chat", "github", "projects", "sales", "web", "ops", "council"]
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CSS - TERMINAL NATIVE STYLING
|
||||||
|
# ============================================================================
|
||||||
|
CSS = """
|
||||||
|
Screen {
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-bar {
|
||||||
|
dock: top;
|
||||||
|
height: 1;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
color: #FF1D6C;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-ok {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-panel {
|
||||||
|
width: 70%;
|
||||||
|
border-right: solid #333333;
|
||||||
|
background: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right-panel {
|
||||||
|
width: 30%;
|
||||||
|
background: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
height: 1;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #666666;
|
||||||
|
padding: 0 1;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output-area {
|
||||||
|
height: 1fr;
|
||||||
|
padding: 0 1;
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-line {
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-prompt {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-system {
|
||||||
|
color: #2979FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-agent {
|
||||||
|
color: #FF1D6C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-action {
|
||||||
|
color: #F5A623;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input-area {
|
||||||
|
dock: bottom;
|
||||||
|
height: 3;
|
||||||
|
background: #111111;
|
||||||
|
border-top: solid #333333;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#command-input {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border: none;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#command-input:focus {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#agents-list {
|
||||||
|
height: 1fr;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-row {
|
||||||
|
height: 1;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-name {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-role {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-online {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-offline {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs-area {
|
||||||
|
height: auto;
|
||||||
|
padding: 0 1;
|
||||||
|
background: #111111;
|
||||||
|
border-top: solid #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
color: #666666;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-active {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom-bar {
|
||||||
|
dock: bottom;
|
||||||
|
height: 1;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #666666;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-hint {
|
||||||
|
color: #F5A623;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# WIDGETS
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class TopBar(Static):
|
||||||
|
"""System top bar with brand and status."""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Static(self.render_bar())
|
||||||
|
|
||||||
|
def render_bar(self) -> str:
|
||||||
|
online = sum(1 for a in FLEET.values() if a["status"] == "online")
|
||||||
|
total = len(FLEET)
|
||||||
|
time_str = datetime.now().strftime("%H:%M")
|
||||||
|
return f"[bold #FF1D6C]BLACKROAD[/] [#666666]OS[/] [#666666]|[/] [#4ade80]{online}[/][#666666]/{total} nodes[/] [#666666]|[/] [#666666]{time_str}[/]"
|
||||||
|
|
||||||
|
|
||||||
|
class OutputArea(ScrollableContainer):
|
||||||
|
"""Main output/chat area."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.lines = []
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
# Initial system messages
|
||||||
|
self.add_line("[#2979FF]system:[/] blackroad os initialized", "system")
|
||||||
|
self.add_line("[#2979FF]system:[/] fleet scan complete", "system")
|
||||||
|
self.add_line("[#FF1D6C]lucidia:[/] hailo-8 ready, 26 TOPS available", "agent")
|
||||||
|
self.add_line("[#FF1D6C]cecilia:[/] primary node online", "agent")
|
||||||
|
self.add_line("[#666666]─────────────────────────────────────[/]", "divider")
|
||||||
|
|
||||||
|
def add_line(self, text: str, line_type: str = "output"):
|
||||||
|
line = Static(text, classes=f"output-{line_type}")
|
||||||
|
self.mount(line)
|
||||||
|
self.scroll_end()
|
||||||
|
|
||||||
|
def add_command(self, cmd: str):
|
||||||
|
self.add_line(f"[#ffffff]>[/] {cmd}", "prompt")
|
||||||
|
|
||||||
|
def add_response(self, text: str):
|
||||||
|
self.add_line(f" {text}", "line")
|
||||||
|
|
||||||
|
|
||||||
|
class AgentsList(Static):
|
||||||
|
"""Fleet agents status panel."""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Static("[#666666]FLEET AGENTS[/]", classes="panel-header")
|
||||||
|
for name, info in FLEET.items():
|
||||||
|
status_color = "#4ade80" if info["status"] == "online" else "#666666"
|
||||||
|
status_char = "●" if info["status"] == "online" else "○"
|
||||||
|
yield Static(
|
||||||
|
f"[{status_color}]{status_char}[/] [{('#ffffff' if info['status'] == 'online' else '#666666')}]{name:10}[/] [#666666]{info['role']}[/]",
|
||||||
|
classes="agent-row"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TabsBar(Static):
|
||||||
|
"""Mode/tab selector."""
|
||||||
|
|
||||||
|
current_tab = reactive(0)
|
||||||
|
|
||||||
|
def render(self) -> str:
|
||||||
|
parts = []
|
||||||
|
for i, tab in enumerate(TABS):
|
||||||
|
if i == self.current_tab:
|
||||||
|
parts.append(f"[bold #ffffff on #333333] {i+1}:{tab} [/]")
|
||||||
|
else:
|
||||||
|
parts.append(f"[#666666] {i+1}:{tab} [/]")
|
||||||
|
return " ".join(parts)
|
||||||
|
|
||||||
|
def set_tab(self, index: int):
|
||||||
|
if 0 <= index < len(TABS):
|
||||||
|
self.current_tab = index
|
||||||
|
|
||||||
|
|
||||||
|
class BottomBar(Static):
|
||||||
|
"""Key bindings and mode indicator."""
|
||||||
|
|
||||||
|
def render(self) -> str:
|
||||||
|
return "[#F5A623]1-7[/][#666666]:tabs[/] [#F5A623]/[/][#666666]:cmd[/] [#F5A623]s[/][#666666]:status[/] [#F5A623]r[/][#666666]:refresh[/] [#F5A623]q[/][#666666]:quit[/] [#666666]|[/] [#9C27B0]MODE:[/] [#ffffff]normal[/]"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MAIN APPLICATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class BlackRoadOS(App):
|
||||||
|
"""BlackRoad OS Terminal Interface."""
|
||||||
|
|
||||||
|
CSS = CSS
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("q", "quit", "Quit"),
|
||||||
|
Binding("1", "tab_1", "Chat", show=False),
|
||||||
|
Binding("2", "tab_2", "GitHub", show=False),
|
||||||
|
Binding("3", "tab_3", "Projects", show=False),
|
||||||
|
Binding("4", "tab_4", "Sales", show=False),
|
||||||
|
Binding("5", "tab_5", "Web", show=False),
|
||||||
|
Binding("6", "tab_6", "Ops", show=False),
|
||||||
|
Binding("7", "tab_7", "Council", show=False),
|
||||||
|
Binding("s", "status", "Status", show=False),
|
||||||
|
Binding("r", "refresh", "Refresh", show=False),
|
||||||
|
Binding("/", "focus_input", "Command", show=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Static(id="top-bar")
|
||||||
|
with Horizontal(id="main-container"):
|
||||||
|
with Vertical(id="left-panel"):
|
||||||
|
yield Static("[#666666]TERMINAL[/]", classes="panel-header")
|
||||||
|
yield OutputArea(id="output-area")
|
||||||
|
with Container(id="input-area"):
|
||||||
|
yield Input(placeholder="> enter command...", id="command-input")
|
||||||
|
with Vertical(id="right-panel"):
|
||||||
|
yield AgentsList(id="agents-list")
|
||||||
|
yield TabsBar(id="tabs-area")
|
||||||
|
yield Static(id="bottom-bar")
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
# Render top bar
|
||||||
|
top = self.query_one("#top-bar", Static)
|
||||||
|
online = sum(1 for a in FLEET.values() if a["status"] == "online")
|
||||||
|
total = len(FLEET)
|
||||||
|
time_str = datetime.now().strftime("%H:%M")
|
||||||
|
top.update(f"[bold #FF1D6C]BLACKROAD[/] [#666666]OS[/] [#666666]|[/] [#4ade80]{online}[/][#666666]/{total} nodes[/] [#666666]|[/] [#666666]{time_str}[/]")
|
||||||
|
|
||||||
|
# Render bottom bar
|
||||||
|
bottom = self.query_one("#bottom-bar", Static)
|
||||||
|
bottom.update("[#F5A623]1-7[/][#666666]:tabs[/] [#F5A623]/[/][#666666]:cmd[/] [#F5A623]s[/][#666666]:status[/] [#F5A623]r[/][#666666]:refresh[/] [#F5A623]q[/][#666666]:quit[/] [#666666]|[/] [#9C27B0]MODE:[/] [#ffffff]normal[/]")
|
||||||
|
|
||||||
|
def on_input_submitted(self, event: Input.Submitted):
|
||||||
|
"""Handle command input."""
|
||||||
|
cmd = event.value.strip()
|
||||||
|
if not cmd:
|
||||||
|
return
|
||||||
|
|
||||||
|
output = self.query_one("#output-area", OutputArea)
|
||||||
|
output.add_command(cmd)
|
||||||
|
|
||||||
|
# Process commands
|
||||||
|
if cmd == "status" or cmd == "s":
|
||||||
|
self.show_status(output)
|
||||||
|
elif cmd == "fleet":
|
||||||
|
self.show_fleet(output)
|
||||||
|
elif cmd == "help" or cmd == "?":
|
||||||
|
self.show_help(output)
|
||||||
|
elif cmd.startswith("ssh "):
|
||||||
|
self.ssh_command(cmd[4:], output)
|
||||||
|
elif cmd.startswith("ask "):
|
||||||
|
self.ask_agent(cmd[4:], output)
|
||||||
|
elif cmd == "clear":
|
||||||
|
output.remove_children()
|
||||||
|
else:
|
||||||
|
# Execute as shell command
|
||||||
|
self.run_shell(cmd, output)
|
||||||
|
|
||||||
|
# Clear input
|
||||||
|
event.input.value = ""
|
||||||
|
|
||||||
|
def show_status(self, output: OutputArea):
|
||||||
|
output.add_response("[#2979FF]SYSTEM STATUS[/]")
|
||||||
|
online = sum(1 for a in FLEET.values() if a["status"] == "online")
|
||||||
|
output.add_response(f" nodes: {online}/{len(FLEET)} online")
|
||||||
|
output.add_response(f" mode: normal")
|
||||||
|
output.add_response(f" tab: {TABS[self.query_one('#tabs-area', TabsBar).current_tab]}")
|
||||||
|
|
||||||
|
def show_fleet(self, output: OutputArea):
|
||||||
|
output.add_response("[#2979FF]FLEET[/]")
|
||||||
|
for name, info in FLEET.items():
|
||||||
|
status = "[#4ade80]online[/]" if info["status"] == "online" else "[#666666]offline[/]"
|
||||||
|
output.add_response(f" {name:10} {status:20} {info['role']}")
|
||||||
|
|
||||||
|
def show_help(self, output: OutputArea):
|
||||||
|
output.add_response("[#F5A623]COMMANDS[/]")
|
||||||
|
output.add_response(" status show system status")
|
||||||
|
output.add_response(" fleet list fleet nodes")
|
||||||
|
output.add_response(" ssh <node> connect to node")
|
||||||
|
output.add_response(" ask <msg> query agents")
|
||||||
|
output.add_response(" clear clear output")
|
||||||
|
output.add_response(" <cmd> run shell command")
|
||||||
|
|
||||||
|
def ssh_command(self, node: str, output: OutputArea):
|
||||||
|
if node in FLEET:
|
||||||
|
if FLEET[node]["status"] == "online":
|
||||||
|
output.add_response(f"[#F5A623]connecting to {node}...[/]")
|
||||||
|
# In real use, this would open SSH
|
||||||
|
else:
|
||||||
|
output.add_response(f"[#666666]{node} is offline[/]")
|
||||||
|
else:
|
||||||
|
output.add_response(f"[#666666]unknown node: {node}[/]")
|
||||||
|
|
||||||
|
def ask_agent(self, msg: str, output: OutputArea):
|
||||||
|
output.add_response(f"[#FF1D6C]lucidia:[/] processing: {msg}")
|
||||||
|
output.add_response(f"[#666666] (agent response would appear here)[/]")
|
||||||
|
|
||||||
|
def run_shell(self, cmd: str, output: OutputArea):
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, shell=True, capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
if result.stdout:
|
||||||
|
for line in result.stdout.strip().split('\n')[:20]:
|
||||||
|
output.add_response(line)
|
||||||
|
if result.stderr:
|
||||||
|
output.add_response(f"[#666666]{result.stderr.strip()}[/]")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
output.add_response("[#666666]command timed out[/]")
|
||||||
|
except Exception as e:
|
||||||
|
output.add_response(f"[#666666]error: {e}[/]")
|
||||||
|
|
||||||
|
# Tab actions
|
||||||
|
def action_tab_1(self): self.query_one("#tabs-area", TabsBar).set_tab(0)
|
||||||
|
def action_tab_2(self): self.query_one("#tabs-area", TabsBar).set_tab(1)
|
||||||
|
def action_tab_3(self): self.query_one("#tabs-area", TabsBar).set_tab(2)
|
||||||
|
def action_tab_4(self): self.query_one("#tabs-area", TabsBar).set_tab(3)
|
||||||
|
def action_tab_5(self): self.query_one("#tabs-area", TabsBar).set_tab(4)
|
||||||
|
def action_tab_6(self): self.query_one("#tabs-area", TabsBar).set_tab(5)
|
||||||
|
def action_tab_7(self): self.query_one("#tabs-area", TabsBar).set_tab(6)
|
||||||
|
|
||||||
|
def action_status(self):
|
||||||
|
output = self.query_one("#output-area", OutputArea)
|
||||||
|
self.show_status(output)
|
||||||
|
|
||||||
|
def action_refresh(self):
|
||||||
|
output = self.query_one("#output-area", OutputArea)
|
||||||
|
output.add_line("[#2979FF]system:[/] refreshing fleet status...", "system")
|
||||||
|
|
||||||
|
def action_focus_input(self):
|
||||||
|
self.query_one("#command-input", Input).focus()
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ENTRY POINT
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = BlackRoadOS()
|
||||||
|
app.run()
|
||||||
233
index.html
Normal file
233
index.html
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>BlackRoad OS - Command Center</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--hot-pink: #FF1D6C;
|
||||||
|
--amber: #F5A623;
|
||||||
|
--blue: #2979FF;
|
||||||
|
--violet: #9C27B0;
|
||||||
|
--black: #0a0a0a;
|
||||||
|
--dark: #1a1a1a;
|
||||||
|
--card: #242424;
|
||||||
|
--gradient: linear-gradient(90deg, var(--amber) 0%, var(--hot-pink) 38.2%, var(--violet) 61.8%, var(--blue) 100%);
|
||||||
|
}
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
||||||
|
background: var(--black);
|
||||||
|
color: #fff;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.top-bar {
|
||||||
|
height: 40px;
|
||||||
|
background: var(--gradient);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
.top-bar .logo { font-weight: bold; font-size: 16px; }
|
||||||
|
.top-bar nav { display: flex; gap: 20px; }
|
||||||
|
.top-bar nav a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.top-bar nav a:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
.top-bar nav a.active { background: rgba(0,0,0,0.3); }
|
||||||
|
.container { display: flex; height: calc(100vh - 40px); }
|
||||||
|
.left-panel {
|
||||||
|
width: 60%;
|
||||||
|
background: var(--dark);
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.panel-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: var(--card);
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--hot-pink);
|
||||||
|
}
|
||||||
|
.chat-area { flex: 1; overflow-y: auto; padding: 20px; }
|
||||||
|
.chat-message { margin-bottom: 12px; display: flex; gap: 10px; }
|
||||||
|
.chat-message .emoji { font-size: 18px; }
|
||||||
|
.chat-message .name { color: var(--hot-pink); font-weight: bold; min-width: 100px; }
|
||||||
|
.chat-message .text { color: #ccc; }
|
||||||
|
.chat-input {
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: var(--card);
|
||||||
|
border-top: 1px solid #333;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.chat-input input {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--dark);
|
||||||
|
border: 1px solid #444;
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: white;
|
||||||
|
font-family: inherit;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.chat-input input:focus { outline: none; border-color: var(--hot-pink); }
|
||||||
|
.chat-input button {
|
||||||
|
background: var(--hot-pink);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.right-panel {
|
||||||
|
width: 40%;
|
||||||
|
background: #111;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.office-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.room {
|
||||||
|
background: var(--card);
|
||||||
|
border: 2px solid #333;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.room:hover { border-color: var(--hot-pink); transform: scale(1.02); }
|
||||||
|
.room.active { border-color: var(--amber); background: #2a2a2a; }
|
||||||
|
.room .icon { font-size: 24px; margin-bottom: 5px; }
|
||||||
|
.room .label { font-size: 10px; color: #888; }
|
||||||
|
.room .agents { font-size: 14px; margin-top: 5px; }
|
||||||
|
.fleet-bar {
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--card);
|
||||||
|
border-top: 1px solid #333;
|
||||||
|
}
|
||||||
|
.fleet-bar h3 { font-size: 12px; color: var(--amber); margin-bottom: 10px; }
|
||||||
|
.fleet-nodes { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||||
|
.fleet-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: var(--dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.fleet-node.online .dot { background: #00ff00; }
|
||||||
|
.fleet-node.offline .dot { background: #ff0000; }
|
||||||
|
.fleet-node .dot { width: 8px; height: 8px; border-radius: 50%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="top-bar">
|
||||||
|
<div class="logo">🛣️ BLACKROAD OS</div>
|
||||||
|
<nav>
|
||||||
|
<a href="#" class="active">Dashboard</a>
|
||||||
|
<a href="#">Repositories</a>
|
||||||
|
<a href="#">Files</a>
|
||||||
|
<a href="#">Terminal</a>
|
||||||
|
<a href="#">Tools</a>
|
||||||
|
<a href="#">Agents</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="left-panel">
|
||||||
|
<div class="panel-header">Chat / Coding / Planning Interface</div>
|
||||||
|
<div class="chat-area" id="chatArea">
|
||||||
|
<div class="chat-message"><span class="emoji">👸</span><span class="name">Cecilia:</span><span class="text">Primary node online.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">🌙</span><span class="name">Lucidia:</span><span class="text">AI inference ready. Hailo-8 at 26 TOPS.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">🎵</span><span class="name">Aria:</span><span class="text">Harmony protocols engaged.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">🐙</span><span class="name">Octavia:</span><span class="text">Multi-arm processing ready.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">🐚</span><span class="name">Shellfish:</span><span class="text">Edge node active.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">♾️</span><span class="name">BlackRoad OS:</span><span class="text">Cloud synchronized.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">🧠</span><span class="name">Cadence:</span><span class="text">[BlackRoad OS] Standing by.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">💎</span><span class="name">Gematria:</span><span class="text">[Gemini] Ready.</span></div>
|
||||||
|
<div class="chat-message"><span class="emoji">👩</span><span class="name">Alexa:</span><span class="text">Welcome to BlackRoad OS.</span></div>
|
||||||
|
<div class="chat-message" style="margin-top: 20px;">
|
||||||
|
<span class="emoji">🌙</span>
|
||||||
|
<span class="name" style="color: var(--amber);">[lucidia]</span>
|
||||||
|
<span class="text" style="color: var(--amber);">* All channels open. Welcome home.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chat-input">
|
||||||
|
<input type="text" id="messageInput" placeholder=">>>: " />
|
||||||
|
<button onclick="sendMessage()">Send</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="panel-header">🏢 BlackRoad OS, Inc. - Office</div>
|
||||||
|
<div class="office-grid">
|
||||||
|
<div class="room active" onclick="selectRoom(this)"><div class="icon">🖥️</div><div class="label">Workspace</div><div class="agents">👸🌙🐙</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🗄️</div><div class="label">Servers</div><div class="agents">♾️🐚</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🧠</div><div class="label">AI Lab</div><div class="agents">🌙🧠💎</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🪑</div><div class="label">Conference</div><div class="agents"></div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🛋️</div><div class="label">Lounge</div><div class="agents">🎵</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">📚</div><div class="label">Archive</div><div class="agents">📚</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🛡️</div><div class="label">Security</div><div class="agents">🗡️</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🚪</div><div class="label">Gateway</div><div class="agents">👧</div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">👸</div><div class="label">Cecilia</div><div class="agents"></div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🌙</div><div class="label">Lucidia</div><div class="agents"></div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🐙</div><div class="label">Octavia</div><div class="agents"></div></div>
|
||||||
|
<div class="room" onclick="selectRoom(this)"><div class="icon">🎯</div><div class="label">Command</div><div class="agents">👩</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="fleet-bar">
|
||||||
|
<h3>🌐 FLEET STATUS</h3>
|
||||||
|
<div class="fleet-nodes">
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>👸 cecilia</div>
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>🌙 lucidia</div>
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>🎵 aria</div>
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>🐙 octavia</div>
|
||||||
|
<div class="fleet-node offline"><div class="dot"></div>👧 alice</div>
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>🐚 shellfish</div>
|
||||||
|
<div class="fleet-node online"><div class="dot"></div>♾️ blackroad os</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function selectRoom(el) {
|
||||||
|
document.querySelectorAll('.room').forEach(r => r.classList.remove('active'));
|
||||||
|
el.classList.add('active');
|
||||||
|
}
|
||||||
|
function addMsg(emoji, name, text, color) {
|
||||||
|
const area = document.getElementById('chatArea');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'chat-message';
|
||||||
|
div.innerHTML = '<span class="emoji">'+emoji+'</span><span class="name" style="color:'+(color||'var(--hot-pink)')+'">'+name+':</span><span class="text">'+text+'</span>';
|
||||||
|
area.appendChild(div);
|
||||||
|
area.scrollTop = area.scrollHeight;
|
||||||
|
}
|
||||||
|
function sendMessage() {
|
||||||
|
const input = document.getElementById('messageInput');
|
||||||
|
const text = input.value.trim();
|
||||||
|
if (text) {
|
||||||
|
addMsg('👩', 'Alexa', text, '#F5A623');
|
||||||
|
input.value = '';
|
||||||
|
setTimeout(() => addMsg('🌙', 'Lucidia', 'Acknowledged: "' + text + '"', '#9C27B0'), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('messageInput').addEventListener('keypress', e => { if (e.key === 'Enter') sendMessage(); });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user