309 lines
11 KiB
Rust
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()
|
|
}
|
|
}
|