Files
AgentCompany/AgentCompany-Rust/crates/app_ui/src/app_root.rs
T

309 lines
11 KiB
Rust

use crate::{
SharedStore,
widgets::{
button::{primary_button, secondary_button},
text_input::TextInput,
},
};
use app_models::AppState;
use domain::{ApprovalState, ProfileSnapshot, SectionId};
use gpui::{App, Context, Entity, SharedString, Window, div, prelude::*, px, rgb};
use storage::NewCompanyInput;
mod approvals;
mod documents;
mod inbox;
mod messages;
mod onboarding;
mod overview;
mod runs;
mod settings;
mod sidebar;
mod workspaces;
pub struct AppRoot {
store: SharedStore,
snapshot: ProfileSnapshot,
state: AppState,
company_name_input: Entity<TextInput>,
mission_input: Entity<TextInput>,
workspace_input: Entity<TextInput>,
message_input: Entity<TextInput>,
document_title_input: Entity<TextInput>,
document_body_input: Entity<TextInput>,
}
impl AppRoot {
pub fn new(store: SharedStore, snapshot: ProfileSnapshot, cx: &mut App) -> Self {
let mut root = Self {
store,
snapshot,
state: AppState::default(),
company_name_input: TextInput::new(cx, "Company name"),
mission_input: TextInput::new(cx, "Mission or thesis direction"),
workspace_input: TextInput::new(cx, "Primary workspace path"),
message_input: TextInput::new(cx, "Board message"),
document_title_input: TextInput::new(cx, "Document title"),
document_body_input: TextInput::new(cx, "Document body"),
};
root.sync_selection();
root
}
fn sync_selection(&mut self) {
if self.state.selected_run_id.is_none() {
self.state.selected_run_id = self.snapshot.runs.first().map(|item| item.id.clone());
}
if self.state.selected_approval_id.is_none() {
self.state.selected_approval_id =
self.snapshot.approvals.first().map(|item| item.id.clone());
}
if self.state.selected_message_id.is_none() {
self.state.selected_message_id = self
.snapshot
.agent_messages
.first()
.map(|item| item.id.clone());
}
if self.state.selected_document_id.is_none() {
self.state.selected_document_id =
self.snapshot.documents.first().map(|item| item.id.clone());
}
if self.state.selected_workspace_id.is_none() {
self.state.selected_workspace_id =
self.snapshot.workspaces.first().map(|item| item.id.clone());
}
}
fn current_company_id(&self) -> Option<&str> {
self.snapshot
.current_company()
.map(|company| company.id.as_str())
}
fn set_status(&mut self, message: impl Into<String>, cx: &mut Context<Self>) {
self.state.set_status(message);
cx.notify();
}
fn reload(&mut self, status: impl Into<String>, cx: &mut Context<Self>) {
let result = {
let store = self.store.lock();
store.snapshot()
};
match result {
Ok(snapshot) => {
self.snapshot = snapshot;
self.sync_selection();
self.state.set_status(status);
cx.notify();
}
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn create_company(&mut self, cx: &mut Context<Self>) {
let input = NewCompanyInput {
name: self.company_name_input.read(cx).value(),
description: self.mission_input.read(cx).value(),
workspace_path: self.workspace_input.read(cx).value(),
execution_mode: "ai_ceo".to_string(),
};
let result = {
let mut store = self.store.lock();
store.create_company(input)
};
match result {
Ok(_) => {
self.company_name_input.update(cx, |input, _| input.clear());
self.mission_input.update(cx, |input, _| input.clear());
self.workspace_input.update(cx, |input, _| input.clear());
self.reload("Company created in the native profile.", cx);
}
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn switch_company(&mut self, company_id: String, cx: &mut Context<Self>) {
let result = {
let mut store = self.store.lock();
store.set_current_company(&company_id)
};
match result {
Ok(_) => self.reload("Company switched.", cx),
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn decide_selected_approval(
&mut self,
state: ApprovalState,
note: &'static str,
cx: &mut Context<Self>,
) {
let Some(company_id) = self.current_company_id().map(str::to_string) else {
self.set_status("Create a company first.", cx);
return;
};
let Some(approval_id) = self.state.selected_approval_id.clone() else {
self.set_status("Choose an approval first.", cx);
return;
};
let result = {
let mut store = self.store.lock();
store.decide_approval(&company_id, &approval_id, state, note)
};
match result {
Ok(_) => self.reload(format!("Approval moved to {}.", state.as_str()), cx),
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn send_message(&mut self, cx: &mut Context<Self>) {
let Some(company_id) = self.current_company_id().map(str::to_string) else {
self.set_status("Create a company first.", cx);
return;
};
let body = self.message_input.read(cx).value();
let result = {
let mut store = self.store.lock();
store.send_board_message(&company_id, &body)
};
match result {
Ok(_) => {
self.message_input.update(cx, |input, _| input.clear());
self.reload("Board message saved.", cx);
}
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn mark_selected_message_read(&mut self, cx: &mut Context<Self>) {
let Some(company_id) = self.current_company_id().map(str::to_string) else {
self.set_status("Create a company first.", cx);
return;
};
let Some(message_id) = self.state.selected_message_id.clone() else {
self.set_status("Choose a message first.", cx);
return;
};
let result = {
let mut store = self.store.lock();
store.mark_message_read(&company_id, &message_id)
};
match result {
Ok(_) => self.reload("Message marked as read.", cx),
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn create_document(&mut self, cx: &mut Context<Self>) {
let Some(company_id) = self.current_company_id().map(str::to_string) else {
self.set_status("Create a company first.", cx);
return;
};
let title = self.document_title_input.read(cx).value();
let body = self.document_body_input.read(cx).value();
let result = {
let mut store = self.store.lock();
store.create_document(&company_id, &title, &body)
};
match result {
Ok(_) => {
self.document_title_input
.update(cx, |input, _| input.clear());
self.document_body_input
.update(cx, |input, _| input.clear());
self.reload("Document saved.", cx);
}
Err(error) => self.set_status(error.to_string(), cx),
}
}
fn metric_card(
&self,
label: impl Into<String>,
value: String,
detail: impl Into<String>,
) -> impl IntoElement + '_ {
let label = label.into();
let detail = detail.into();
div()
.flex_1()
.p_4()
.gap_2()
.border_1()
.rounded_md()
.border_color(rgb(0xd9cdbf))
.bg(rgb(0xfffbf5))
.child(div().text_sm().text_color(rgb(0x6f665d)).child(label))
.child(div().text_3xl().text_color(rgb(0x171411)).child(value))
.child(div().text_sm().text_color(rgb(0x6f665d)).child(detail))
}
}
impl Render for AppRoot {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.snapshot.companies.is_empty() {
return self.render_onboarding(cx).into_any_element();
}
let header = self
.snapshot
.current_company()
.map(|company| format!("{} · {}", company.name, company.execution_mode))
.unwrap_or_else(|| "AgentCompany".to_string());
div()
.size_full()
.flex()
.bg(rgb(0xf3efe8))
.child(self.render_sidebar(cx))
.child(
div()
.flex_1()
.flex()
.flex_col()
.child(
div()
.px_6()
.py_4()
.border_b_1()
.border_color(rgb(0xd9cdbf))
.bg(rgb(0xf8f2e9))
.child(div().text_xl().text_color(rgb(0x171411)).child(header))
.child(div().text_sm().text_color(rgb(0x6f665d)).child(
self.state.status_line.clone().unwrap_or_else(|| {
"Native Rust shell is reading the shared local profile."
.to_string()
}),
)),
)
.child(
div()
.id("main-scroll")
.flex_1()
.overflow_scroll()
.p_6()
.child(match self.state.section {
SectionId::Overview => self.render_overview().into_any_element(),
SectionId::Runs => self.render_runs(cx).into_any_element(),
SectionId::Inbox => self.render_inbox().into_any_element(),
SectionId::Messages => self.render_messages(cx).into_any_element(),
SectionId::Approvals => {
self.render_approvals(cx).into_any_element()
}
SectionId::Documents => {
self.render_documents(cx).into_any_element()
}
SectionId::Workspaces => {
self.render_workspaces(cx).into_any_element()
}
SectionId::Settings => self.render_settings().into_any_element(),
}),
),
)
.into_any_element()
}
}