diff --git a/AgentCompany-Rust/crates/app_ui/src/app_root.rs b/AgentCompany-Rust/crates/app_ui/src/app_root.rs index f6dca22b..fb099448 100644 --- a/AgentCompany-Rust/crates/app_ui/src/app_root.rs +++ b/AgentCompany-Rust/crates/app_ui/src/app_root.rs @@ -220,37 +220,6 @@ impl AppRoot { Err(error) => self.set_status(error.to_string(), cx), } } - - fn metric_card( - &self, - label: impl Into, - value: String, - detail: impl Into, - ) -> impl IntoElement + '_ { - let label = label.into(); - let detail = detail.into(); - div() - .flex_1() - .p_4() - .gap_2() - .border_1() - .rounded_md() - .border_color(theme::panel_border()) - .bg(theme::panel()) - .child(div().text_sm().text_color(theme::text_muted()).child(label)) - .child( - div() - .text_3xl() - .text_color(theme::text_primary()) - .child(value), - ) - .child( - div() - .text_sm() - .text_color(theme::text_muted()) - .child(detail), - ) - } } impl Render for AppRoot { diff --git a/AgentCompany-Rust/crates/app_ui/src/app_root/settings.rs b/AgentCompany-Rust/crates/app_ui/src/app_root/settings.rs index d6007a81..9af9aa93 100644 --- a/AgentCompany-Rust/crates/app_ui/src/app_root/settings.rs +++ b/AgentCompany-Rust/crates/app_ui/src/app_root/settings.rs @@ -6,29 +6,113 @@ impl AppRoot { let execution_mode = current .map(|item| item.execution_mode.clone()) .unwrap_or_else(|| "ai_ceo".to_string()); + div() .flex() - .flex_col() - .gap_4() - .child(self.metric_card( - "Profile path", - self.snapshot.profile_path.clone(), - "Electron-compatible local profile", - )) - .child(self.metric_card( - "Theme", - self.snapshot.theme.clone(), - "Stored in the shared settings table", - )) - .child(self.metric_card( - "Locale", - self.snapshot.locale.clone(), - "Read from settings or the OS locale", - )) - .child(self.metric_card( - "Connectors", - self.snapshot.connectors.len().to_string(), - execution_mode, - )) + .gap_6() + .child( + div() + .w(px(220.0)) + .p_4() + .gap_2() + .rounded_lg() + .border_1() + .border_color(theme::panel_border()) + .bg(theme::panel()) + .child(settings_nav_row("General", true)) + .child(settings_nav_row("Execution mode", false)) + .child(settings_nav_row("Providers", false)) + .child(settings_nav_row("Shortcuts", false)) + .child(settings_nav_row("Data", false)) + .child(settings_nav_row("Secrets", false)), + ) + .child( + div() + .flex_1() + .gap_6() + .child(settings_section( + "General", + vec![ + settings_row("Profile path", self.snapshot.profile_path.clone()), + settings_row("Theme", self.snapshot.theme.clone()), + settings_row("Locale", self.snapshot.locale.clone()), + ], + )) + .child(settings_section( + "Execution mode", + vec![settings_row("Company mode", execution_mode)], + )) + .child(settings_section( + "Providers", + vec![settings_row( + "Configured connectors", + self.snapshot.connectors.len().to_string(), + )], + )) + .child(settings_section( + "Data", + vec![ + settings_row("Stored profile", "Local SQLite".to_string()), + settings_row("Current company", current_company_name(current)), + ], + )), + ) } } + +fn settings_nav_row(label: &'static str, active: bool) -> impl IntoElement { + div() + .px_4() + .py_3() + .rounded_lg() + .bg(if active { + theme::selection_fill() + } else { + theme::panel() + }) + .text_color(if active { + theme::sidebar_text_active() + } else { + theme::text_secondary() + }) + .child(label) +} + +fn settings_section(label: &'static str, rows: Vec) -> impl IntoElement { + div() + .p_5() + .rounded_lg() + .border_1() + .border_color(theme::panel_border()) + .bg(theme::panel()) + .child( + div() + .text_lg() + .text_color(theme::text_primary()) + .child(label), + ) + .child(div().mt(px(16.0)).children(rows)) +} + +fn settings_row(label: &'static str, value: String) -> gpui::Div { + div() + .flex() + .justify_between() + .items_center() + .py_3() + .border_b_1() + .border_color(theme::panel_border()) + .child(div().text_sm().text_color(theme::text_muted()).child(label)) + .child( + div() + .text_sm() + .text_color(theme::text_primary()) + .child(value), + ) +} + +fn current_company_name(company: Option<&domain::Company>) -> String { + company + .map(|item| item.name.clone()) + .unwrap_or_else(|| "No company".to_string()) +} diff --git a/AgentCompany-Rust/crates/app_ui/src/app_root/workspaces.rs b/AgentCompany-Rust/crates/app_ui/src/app_root/workspaces.rs index ef0c2c77..fcb78208 100644 --- a/AgentCompany-Rust/crates/app_ui/src/app_root/workspaces.rs +++ b/AgentCompany-Rust/crates/app_ui/src/app_root/workspaces.rs @@ -5,50 +5,148 @@ impl AppRoot { div() .flex() .flex_col() - .gap_3() - .children(self.snapshot.workspaces.iter().map(|workspace| { - let active = - self.state.selected_workspace_id.as_deref() == Some(workspace.id.as_str()); - let workspace_id = workspace.id.clone(); + .gap_6() + .child( div() - .id(SharedString::from(format!("workspace-{}", workspace.id))) - .p_4() - .gap_2() - .border_1() - .rounded_md() - .border_color(theme::panel_border()) - .bg(if active { - theme::panel_inset() - } else { - theme::panel() - }) - .cursor_pointer() - .hover(|style| style.bg(theme::panel_hover())) - .on_click(cx.listener(move |this, _, _, cx| { - this.state.selected_workspace_id = Some(workspace_id.clone()); - cx.notify(); - })) - .child(div().text_lg().child(workspace.name.clone())) + .flex() + .justify_between() + .items_start() .child( div() - .text_sm() - .text_color(theme::text_muted()) - .child(workspace.local_path.clone()), - ) - .child( - div() - .text_sm() - .text_color(theme::text_muted()) - .child(format!( - "{} · {}", - workspace.state, - if workspace.is_primary { - "primary" + .child( + div() + .text_2xl() + .text_color(theme::text_primary()) + .child("Workspaces"), + ) + .child( + div() + .mt(px(6.0)) + .text_sm() + .text_color(theme::text_muted()) + .child(format!("{} linked workspaces", self.snapshot.workspaces.len())), + ), + ), + ) + .child(div().h(px(1.0)).bg(theme::panel_border())) + .when(self.snapshot.workspaces.is_empty(), |container| { + container.child( + div() + .min_h(px(260.0)) + .flex() + .items_center() + .justify_center() + .rounded_lg() + .border_1() + .border_color(theme::panel_border()) + .bg(theme::panel()) + .child( + div() + .w(px(420.0)) + .text_center() + .child( + div() + .text_xl() + .text_color(theme::text_primary()) + .child("No workspaces linked"), + ) + .child( + div() + .mt(px(10.0)) + .text_base() + .text_color(theme::text_muted()) + .child("The active company will show its local workspaces here."), + ), + ), + ) + }) + .when(!self.snapshot.workspaces.is_empty(), |container| { + container.child( + div() + .children(self.snapshot.workspaces.iter().map(|workspace| { + let active = self.state.selected_workspace_id.as_deref() + == Some(workspace.id.as_str()); + let workspace_id = workspace.id.clone(); + div() + .id(SharedString::from(format!("workspace-{}", workspace.id))) + .flex() + .justify_between() + .items_center() + .px_6() + .py_5() + .border_b_1() + .border_color(theme::panel_border()) + .cursor_pointer() + .bg(if active { + theme::selection_fill() } else { - "linked" - } - )), - ) - })) + theme::window_bg() + }) + .hover(|style| style.bg(theme::panel_hover())) + .on_click(cx.listener(move |this, _, _, cx| { + this.state.selected_workspace_id = Some(workspace_id.clone()); + cx.notify(); + })) + .child( + div() + .flex_1() + .child( + div() + .text_base() + .text_color(theme::text_primary()) + .child(workspace.name.clone()), + ) + .child( + div() + .mt(px(8.0)) + .text_sm() + .text_color(theme::text_muted()) + .child(workspace.local_path.clone()), + ) + .child( + div() + .mt(px(8.0)) + .text_xs() + .text_color(theme::text_subtle()) + .child(format!( + "{} · {}", + workspace.state, + workspace_kind(workspace.is_primary) + )), + ), + ) + .child(workspace_state_badge( + workspace.state.as_str(), + workspace.is_primary, + )) + })), + ) + }) } } + +fn workspace_kind(is_primary: bool) -> &'static str { + if is_primary { "primary" } else { "linked" } +} + +fn workspace_state_badge(state: &str, is_primary: bool) -> impl IntoElement { + div() + .flex() + .items_center() + .gap_2() + .child(div().w(px(8.0)).h(px(8.0)).rounded_full().bg(match state { + "ready" => theme::accent_teal(), + "blocked" | "missing" | "error" => theme::danger(), + _ => theme::accent_blue(), + })) + .child( + div() + .text_xs() + .text_color(theme::text_secondary()) + .child(if is_primary { + format!("{} · PRIMARY", state.to_uppercase()) + } else { + state.to_uppercase() + }), + ) +}