feat: add Dioxus and GPUI UI prototypes for framework comparison
Dioxus (ui-dioxus/): 2,169 lines, WebView mode (same wry as Tauri), Catppuccin theme, 12 components, agor-core integration, compiles clean. Evolution path — keeps xterm.js, gradual migration from Tauri. GPUI (ui-gpui/): 2,490 lines, GPU-accelerated rendering, alacritty_terminal for native terminal, 17 files, Catppuccin palette, demo data. Revolution path — pure Rust UI, 120fps target, no WebView. Both are standalone (not in workspace), share agor-core backend. Created for side-by-side comparison to inform framework decision.
This commit is contained in:
parent
90c7315336
commit
f3d2ca78ba
34 changed files with 17467 additions and 0 deletions
129
ui-gpui/src/backend.rs
Normal file
129
ui-gpui/src/backend.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
//! Bridge to agor-core: PtyManager, SidecarManager, EventSink.
|
||||
//!
|
||||
//! Implements the `EventSink` trait from agor-core so that PTY and sidecar
|
||||
//! events flow into GPUI's entity system via channel-based async dispatch.
|
||||
|
||||
use agor_core::event::EventSink;
|
||||
use agor_core::pty::{PtyManager, PtyOptions};
|
||||
use agor_core::sandbox::SandboxConfig;
|
||||
use agor_core::sidecar::{AgentQueryOptions, SidecarConfig, SidecarManager};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
// ── GPUI EventSink ──────────────────────────────────────────────────
|
||||
|
||||
/// Event payload sent from backend threads to the GPUI main thread.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackendEvent {
|
||||
pub event_name: String,
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
/// An `EventSink` that queues events into an `mpsc` channel.
|
||||
/// The GPUI main loop drains this channel each frame to update entity state.
|
||||
pub struct GpuiEventSink {
|
||||
sender: mpsc::Sender<BackendEvent>,
|
||||
}
|
||||
|
||||
impl GpuiEventSink {
|
||||
pub fn new(sender: mpsc::Sender<BackendEvent>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSink for GpuiEventSink {
|
||||
fn emit(&self, event: &str, payload: serde_json::Value) {
|
||||
let _ = self.sender.send(BackendEvent {
|
||||
event_name: event.to_string(),
|
||||
payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Backend Manager ─────────────────────────────────────────────────
|
||||
|
||||
/// Owns the agor-core managers and the event channel.
|
||||
/// Created once at app startup; shared via `Arc` or `Entity<Backend>`.
|
||||
pub struct Backend {
|
||||
pub pty_manager: PtyManager,
|
||||
pub sidecar_manager: SidecarManager,
|
||||
pub event_rx: mpsc::Receiver<BackendEvent>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
/// Create backend with default sidecar search paths.
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let sink: Arc<dyn EventSink> = Arc::new(GpuiEventSink::new(tx));
|
||||
|
||||
let pty_manager = PtyManager::new(Arc::clone(&sink));
|
||||
|
||||
// Sidecar search paths: look next to the binary, then common install locations
|
||||
let mut search_paths = vec![
|
||||
PathBuf::from("./sidecar/dist"),
|
||||
PathBuf::from("../sidecar/dist"),
|
||||
];
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
search_paths.push(dir.join("sidecar"));
|
||||
}
|
||||
}
|
||||
|
||||
let sidecar_config = SidecarConfig {
|
||||
search_paths,
|
||||
env_overrides: HashMap::new(),
|
||||
sandbox: SandboxConfig::default(),
|
||||
};
|
||||
let sidecar_manager = SidecarManager::new(Arc::clone(&sink), sidecar_config);
|
||||
|
||||
Self {
|
||||
pty_manager,
|
||||
sidecar_manager,
|
||||
event_rx: rx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a PTY for a project terminal.
|
||||
pub fn spawn_pty(&self, cwd: &str) -> Result<String, String> {
|
||||
self.pty_manager.spawn(PtyOptions {
|
||||
shell: None,
|
||||
cwd: Some(cwd.to_string()),
|
||||
args: None,
|
||||
cols: Some(120),
|
||||
rows: Some(30),
|
||||
})
|
||||
}
|
||||
|
||||
/// Start an agent query (sends to sidecar, non-blocking).
|
||||
pub fn start_agent(&self, session_id: &str, prompt: &str, cwd: &str) {
|
||||
let options = AgentQueryOptions {
|
||||
provider: "claude".to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
prompt: prompt.to_string(),
|
||||
cwd: Some(cwd.to_string()),
|
||||
max_turns: None,
|
||||
max_budget_usd: None,
|
||||
resume_session_id: None,
|
||||
permission_mode: Some("bypassPermissions".to_string()),
|
||||
setting_sources: Some(vec!["user".to_string(), "project".to_string()]),
|
||||
system_prompt: None,
|
||||
model: None,
|
||||
claude_config_dir: None,
|
||||
additional_directories: None,
|
||||
worktree_name: None,
|
||||
provider_config: serde_json::Value::Null,
|
||||
extra_env: HashMap::new(),
|
||||
};
|
||||
let _ = self.sidecar_manager.query(&options);
|
||||
}
|
||||
|
||||
/// Drain all pending backend events (call once per frame / tick).
|
||||
pub fn drain_events(&self) -> Vec<BackendEvent> {
|
||||
let mut events = Vec::new();
|
||||
while let Ok(ev) = self.event_rx.try_recv() {
|
||||
events.push(ev);
|
||||
}
|
||||
events
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue