126 lines
4.4 KiB
Rust
126 lines
4.4 KiB
Rust
/// Agent Orchestrator — Dioxus 0.7 Desktop Prototype
|
|
///
|
|
/// This prototype demonstrates the core Agent Orchestrator experience
|
|
/// built with Dioxus 0.7 (desktop mode via wry/WebKit2GTK).
|
|
///
|
|
/// Architecture comparison vs Tauri+Svelte:
|
|
///
|
|
/// | Aspect | Tauri+Svelte (current) | Dioxus (this prototype) |
|
|
/// |---------------------|-------------------------------|-------------------------------|
|
|
/// | UI rendering | WebView (Svelte -> HTML) | WebView (RSX -> HTML) |
|
|
/// | State management | Svelte 5 runes ($state) | Dioxus signals (use_signal) |
|
|
/// | Backend bridge | Tauri IPC (invoke/listen) | Direct Rust (no IPC!) |
|
|
/// | Terminal | xterm.js via WebView | xterm.js via WebView (same) |
|
|
/// | Reactivity model | Compiler-based (Svelte 5) | Runtime signals (fine-grained)|
|
|
/// | Component model | .svelte files (HTML+JS+CSS) | Rust functions (rsx! macro) |
|
|
/// | Type safety | TypeScript (compile-time) | Rust (compile-time, stronger) |
|
|
/// | Hot reload | Vite HMR | dx serve (RSX hot reload) |
|
|
///
|
|
/// Key advantage: no IPC serialization boundary. Backend state (PtyManager,
|
|
/// SidecarManager) is directly accessible from UI code as typed Rust values.
|
|
|
|
#[allow(dead_code)]
|
|
mod backend;
|
|
mod components;
|
|
mod state;
|
|
mod theme;
|
|
|
|
use dioxus::prelude::*;
|
|
|
|
use components::command_palette::CommandPalette;
|
|
use components::project_grid::ProjectGrid;
|
|
use components::settings::SettingsPanel;
|
|
use components::sidebar::Sidebar;
|
|
use components::status_bar::{FleetState, StatusBar};
|
|
use state::demo_projects;
|
|
|
|
fn main() {
|
|
// Native/Blitz mode: wgpu renderer, no WebView
|
|
// CSS is injected via the <style> element in the RSX tree (see App component)
|
|
dioxus::launch(App);
|
|
}
|
|
|
|
/// Root application component.
|
|
///
|
|
/// Layout: sidebar rail | optional drawer | project grid | status bar
|
|
/// Same structure as the Svelte app's App.svelte.
|
|
#[allow(non_snake_case)]
|
|
fn App() -> Element {
|
|
// Global UI state
|
|
let mut settings_open = use_signal(|| false);
|
|
let mut palette_open = use_signal(|| false);
|
|
|
|
// Projects — in the real app, loaded from groups.json via groups-bridge
|
|
let projects = use_signal(|| demo_projects());
|
|
|
|
// Fleet state — derived from all project health stores
|
|
let fleet = FleetState {
|
|
running: 1,
|
|
idle: 1,
|
|
stalled: 0,
|
|
total_cost: 0.1694,
|
|
total_tokens: 48_700,
|
|
project_count: projects.read().len(),
|
|
};
|
|
|
|
// Keyboard shortcuts
|
|
let on_keydown = move |e: KeyboardEvent| {
|
|
let modifiers = e.modifiers();
|
|
let is_ctrl = modifiers.ctrl();
|
|
|
|
match e.key() {
|
|
// Ctrl+K: Command palette
|
|
Key::Character(ref c) if is_ctrl && c == "k" => {
|
|
palette_open.set(true);
|
|
}
|
|
// Ctrl+,: Toggle settings
|
|
Key::Character(ref c) if is_ctrl && c == "," => {
|
|
let current = *settings_open.read();
|
|
settings_open.set(!current);
|
|
}
|
|
// Ctrl+B: Toggle sidebar drawer
|
|
Key::Character(ref c) if is_ctrl && c == "b" => {
|
|
let current = *settings_open.read();
|
|
settings_open.set(!current);
|
|
}
|
|
// Escape: close overlays
|
|
Key::Escape => {
|
|
if *palette_open.read() {
|
|
palette_open.set(false);
|
|
} else if *settings_open.read() {
|
|
settings_open.set(false);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
};
|
|
|
|
rsx! {
|
|
style { {theme::generate_css()} }
|
|
div {
|
|
class: "app-shell",
|
|
onkeydown: on_keydown,
|
|
tabindex: "0",
|
|
|
|
// Main body: sidebar + optional drawer + workspace
|
|
div { class: "app-body",
|
|
// Left sidebar rail
|
|
Sidebar { settings_open: settings_open }
|
|
|
|
// Optional drawer panel (settings)
|
|
if *settings_open.read() {
|
|
SettingsPanel {}
|
|
}
|
|
|
|
// Main workspace: project grid
|
|
ProjectGrid { projects: projects.read().clone() }
|
|
}
|
|
|
|
// Bottom status bar
|
|
StatusBar { fleet: fleet }
|
|
|
|
// Command palette overlay
|
|
CommandPalette { visible: palette_open }
|
|
}
|
|
}
|
|
}
|