use eframe::egui;
use eframe::egui::Color32;
use eframe::egui::{FontFamily, FontId, Rounding, TextStyle, Visuals};
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tokio::sync::{broadcast, mpsc};
mod event;
mod app;
mod agent;
mod agent_blueprint;
mod ag_parser;
mod ai_provider;
mod config;
mod database;
mod filesystem;
mod gridshell;
mod pipeline;
mod arena;
pub use event::Event;
pub use agent::{generate_procedural_personality, spawn_agents_for_directory, ProgramAgent};
use ai_provider::{run_ai_engine, AiRequest};
use config::Config;
use database::Database;
use app::{GridApp, DigitizationState};
use gridshell::GridShell;
use filesystem::SpatialKnowledgeFS;
fn main() -> eframe::Result<()> {
let config = match Config::load() {
Ok(cfg) => cfg,
Err(_) => return Ok(()), };
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let rt_handle = rt.handle().clone();
let rt_handle_clone = rt.handle().clone();
let shared_config = Arc::new(Mutex::new(config));
let typing_agents = Arc::new(Mutex::new(HashSet::<String>::new()));
let (tx, mut monitor_rx) = broadcast::channel::<Event>(100);
let (ai_tx, ai_rx) = mpsc::channel::<AiRequest>(32);
let tx_for_ai = tx.clone();
let config_for_ai = shared_config.clone();
let ai_task = rt.spawn(run_ai_engine(ai_rx, tx_for_ai, config_for_ai));
let messages = Arc::new(Mutex::new(Vec::new()));
let messages_clone = messages.clone();
let typing_agents_for_listener = typing_agents.clone();
let device_name = sysinfo::System::host_name().unwrap_or_else(|| "Unknown".to_string());
let grid_name = format!("The {} Grid", device_name);
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([600.0, 500.0]),
..Default::default()
};
let grid_name_for_title = grid_name.clone();
eframe::run_native(
&grid_name_for_title,
options,
Box::new(move |cc| { let ctx = cc.egui_ctx.clone();
let mut visuals = Visuals::dark();
visuals.panel_fill = Color32::from_rgb(0, 0, 0); visuals.window_fill = Color32::from_rgb(0, 0, 0);
visuals.faint_bg_color = Color32::from_rgb(20, 20, 20);
visuals.extreme_bg_color = Color32::from_rgb(10, 10, 10);
visuals.override_text_color = Some(Color32::from_rgb(170, 170, 170));
let rounding = Rounding::same(0.0);
visuals.window_rounding = rounding;
visuals.menu_rounding = rounding;
visuals.widgets.noninteractive.rounding = rounding;
visuals.widgets.inactive.rounding = rounding;
visuals.widgets.hovered.rounding = rounding;
visuals.widgets.active.rounding = rounding;
visuals.widgets.open.rounding = rounding;
ctx.set_visuals(visuals);
let mut style = (*ctx.style()).clone();
style.text_styles.insert(TextStyle::Body, FontId::new(16.0, FontFamily::Monospace));
style.text_styles.insert(TextStyle::Button, FontId::new(16.0, FontFamily::Monospace));
style.text_styles.insert(TextStyle::Heading, FontId::new(18.0, FontFamily::Monospace));
ctx.set_style(style);
rt_handle_clone.spawn(async move {
while let Ok(event) = monitor_rx.recv().await {
let mut typing_guard = typing_agents_for_listener.lock().unwrap();
match event.action.as_str() {
"is_typing" => {
typing_guard.insert(event.sender.clone());
}
"stops_typing" => {
typing_guard.remove(&event.sender);
}
_ => {
typing_guard.remove(&event.sender);
if let Ok(mut msgs) = messages_clone.lock() {
msgs.push(event);
if msgs.len() > 500 {
msgs.remove(0); }
}
}
}
ctx.request_repaint();
}
});
let current_dir = std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| "/".to_string());
let initial_db = if Path::new("the_grid.db").exists() {
Database::new().ok().map(|db| Arc::new(Mutex::new(db)))
} else {
None
};
let (initial_tasks, initial_names) = spawn_agents_for_directory(¤t_dir, &rt_handle, tx.clone(), ai_tx.clone(), initial_db.clone(), &grid_name);
let _ = tx.send(Event {
sender: "System".to_string(),
action: "announces".to_string(),
content: format!("{} is now online.", grid_name),
});
let user_name = whoami::username();
let skfs = Arc::new(Mutex::new(SpatialKnowledgeFS::new()));
{
let mut skfs_lock = skfs.lock().unwrap();
skfs_lock.initialize_with_defaults();
}
let mut gridshell = GridShell::new(tx.clone(), skfs.clone());
if let Err(e) = gridshell.initialize() {
eprintln!("Failed to initialize GridShell: {}", e);
}
let color_palette = vec![
Color32::from_rgb(0, 255, 0), Color32::from_rgb(0, 255, 255), Color32::from_rgb(255, 170, 0), Color32::from_rgb(255, 0, 255), Color32::from_rgb(255, 255, 85), Color32::from_rgb(255, 85, 85), Color32::from_rgb(170, 170, 255), ];
let emoji_palette: Vec<String> = ["đ¤", "đŊ", "đž", "đ§ ", "âī¸", "đĄ", "đĄ", "đž", "đŋ", "đšī¸", "đī¸", "đĨī¸", "đģ", "đąī¸", "đ"]
.iter().map(|&s| s.to_string()).collect();
Box::new(GridApp {
tx, messages,
input: String::new(),
current_dir, user_name,
rt_handle, agent_tasks: initial_tasks,
shared_config,
typing_agents: typing_agents.clone(),
ai_tx,
ai_task,
agent_names: initial_names,
db: initial_db,
show_map: false,
colors: HashMap::new(),
color_palette,
next_color_index: 0,
show_emojis: false,
emoji_palette,
show_thoughts: true,
show_feels: true,
show_secrets: false,
invoked_tools: HashSet::new(),
rel_cache: HashMap::new(),
last_rel_update: Instant::now(),
last_active_agent: None,
map_user_pos: egui::pos2(0.0, 0.0),
file_positions: HashMap::new(),
agent_3d_positions: HashMap::new(),
digitization_state: DigitizationState::Booting(0),
digitization_log: Vec::new(),
last_log_tick: Instant::now(),
camera_angle: 0.0,
gridshell: gridshell,
skfs: skfs,
grid_name: grid_name,
})
}),
)
}