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.
155 lines
4.9 KiB
Rust
155 lines
4.9 KiB
Rust
/// AgentPane — message list + prompt input for a single agent session.
|
|
///
|
|
/// Mirrors the Svelte app's AgentPane.svelte: sans-serif font, tool call
|
|
/// pairing with collapsible details, status strip, prompt bar.
|
|
|
|
use dioxus::prelude::*;
|
|
|
|
use crate::state::{AgentMessage, AgentSession, AgentStatus, MessageRole};
|
|
|
|
#[component]
|
|
pub fn AgentPane(
|
|
session: Signal<AgentSession>,
|
|
project_name: String,
|
|
) -> Element {
|
|
let mut prompt_text = use_signal(|| String::new());
|
|
|
|
let status = session.read().status;
|
|
let is_running = status == AgentStatus::Running;
|
|
|
|
let mut do_submit = move || {
|
|
let text = prompt_text.read().clone();
|
|
if text.trim().is_empty() || is_running {
|
|
return;
|
|
}
|
|
|
|
// Add user message to the session
|
|
let msg = AgentMessage {
|
|
id: uuid::Uuid::new_v4().to_string(),
|
|
role: MessageRole::User,
|
|
content: text.clone(),
|
|
tool_name: None,
|
|
tool_output: None,
|
|
};
|
|
|
|
session.write().messages.push(msg);
|
|
session.write().status = AgentStatus::Running;
|
|
prompt_text.set(String::new());
|
|
|
|
// In a real implementation, this would call:
|
|
// backend.query_agent(&session_id, &text, &cwd)
|
|
// For the prototype, we simulate a response after a brief delay.
|
|
};
|
|
|
|
let on_click = move |_: MouseEvent| {
|
|
do_submit();
|
|
};
|
|
|
|
let on_keydown = move |e: KeyboardEvent| {
|
|
if e.key() == Key::Enter && !e.modifiers().shift() {
|
|
do_submit();
|
|
}
|
|
};
|
|
|
|
rsx! {
|
|
div { class: "agent-pane",
|
|
// Message list
|
|
div { class: "message-list",
|
|
for msg in session.read().messages.iter() {
|
|
MessageBubble {
|
|
key: "{msg.id}",
|
|
message: msg.clone(),
|
|
}
|
|
}
|
|
|
|
// Running indicator
|
|
if is_running {
|
|
div {
|
|
class: "message assistant",
|
|
style: "opacity: 0.6; font-style: italic;",
|
|
div { class: "message-role", "Claude" }
|
|
div { class: "message-text", "Thinking..." }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Status strip
|
|
div { class: "agent-status",
|
|
span {
|
|
class: "status-badge {status.css_class()}",
|
|
"{status.label()}"
|
|
}
|
|
span { "Session: {truncate_id(&session.read().session_id)}" }
|
|
span { "Model: {session.read().model}" }
|
|
if session.read().cost_usd > 0.0 {
|
|
span { class: "status-cost", "${session.read().cost_usd:.4}" }
|
|
}
|
|
}
|
|
|
|
// Prompt bar
|
|
div { class: "prompt-bar",
|
|
input {
|
|
class: "prompt-input",
|
|
r#type: "text",
|
|
placeholder: "Ask Claude...",
|
|
value: "{prompt_text}",
|
|
oninput: move |e| prompt_text.set(e.value()),
|
|
onkeydown: on_keydown,
|
|
disabled: is_running,
|
|
}
|
|
button {
|
|
class: "prompt-send",
|
|
onclick: on_click,
|
|
disabled: is_running || prompt_text.read().trim().is_empty(),
|
|
"Send"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A single message bubble with role label and optional tool details.
|
|
#[component]
|
|
fn MessageBubble(message: AgentMessage) -> Element {
|
|
let role_class = message.role.css_class();
|
|
let has_tool_output = message.tool_output.is_some();
|
|
|
|
rsx! {
|
|
div { class: "message {role_class}",
|
|
div { class: "message-role", "{message.role.label()}" }
|
|
|
|
if message.role == MessageRole::Tool {
|
|
// Tool call: show name and collapsible output
|
|
div { class: "message-text",
|
|
if let Some(ref tool_name) = message.tool_name {
|
|
span {
|
|
style: "color: var(--ctp-teal); font-weight: 600;",
|
|
"[{tool_name}] "
|
|
}
|
|
}
|
|
"{message.content}"
|
|
}
|
|
if has_tool_output {
|
|
details { class: "tool-details",
|
|
summary { "Show output" }
|
|
div { class: "tool-output",
|
|
"{message.tool_output.as_deref().unwrap_or(\"\")}"
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// User or assistant message
|
|
div { class: "message-text", "{message.content}" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Truncate a UUID to first 8 chars for display.
|
|
fn truncate_id(id: &str) -> String {
|
|
if id.len() > 8 {
|
|
format!("{}...", &id[..8])
|
|
} else {
|
|
id.to_string()
|
|
}
|
|
}
|