use crate::{model::workspace::Workspace, view::settings::SettingsWindow};
use bezel::{
gpui::{AnyElement, ClipboardItem, Context, div, prelude::*, px},
motion::{Fade, Painter},
theme::{TextStyle, Theme, Typeset},
ui::{
icons,
widgets::{ButtonStyle, Buttons, Controls, Scaffolding},
},
};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Switch {
Serve,
Write,
}
impl Switch {
const ALL: [Self; 2] = [Self::Serve, Self::Write];
fn copy(self) -> (&'static [u8], &'static str, &'static str) {
match self {
Self::Serve => (
icons::development::Plug,
"Serve",
"Offers your agents the tools that work a project's boards.",
),
Self::Write => (
icons::text::Pencil,
"Agents may edit",
"Off offers the tools that read a project and none that change it.",
),
}
}
fn on(self, workspace: &Workspace) -> bool {
match self {
Self::Serve => workspace.settings.mcp.serve,
Self::Write => workspace.settings.mcp.write,
}
}
}
impl SettingsWindow {
pub(super) fn mcp_body(&self, cx: &Context<Self>) -> AnyElement {
let theme = Theme::of(cx).clone();
let workspace = self.workspace.read(cx);
let quiet = workspace.settings.mcp.serve && !workspace.settings.features.sessions;
theme
.group_box()
.children(Switch::ALL.map(|switch| self.mcp_switch(switch, cx)))
.children(quiet.then(|| {
theme.card_row(false).child(
div()
.text_style(TextStyle::Callout)
.text_color(theme.text_muted)
.child("Sessions is off in Features, so nothing runs to reach it."),
)
}))
.children(self.mcp_address(cx))
.into_any_element()
}
fn mcp_switch(&self, switch: Switch, cx: &Context<Self>) -> AnyElement {
let theme = Theme::of(cx).clone();
let on = switch.on(self.workspace.read(cx));
let (glyph, title, blurb) = switch.copy();
theme
.card_row(switch == Switch::Serve)
.child(
div()
.flex_none()
.size(px(18.))
.flex()
.items_center()
.justify_center()
.child(
icons::icon(glyph)
.size(px(16.))
.flex_none()
.text_color(theme.text_muted),
),
)
.child(
div()
.flex_1()
.min_w_0()
.flex()
.flex_col()
.child(theme.row_title(title))
.child(
div()
.mt(px(4.))
.truncate()
.text_style(TextStyle::Subheadline)
.text_color(theme.text_muted)
.child(blurb),
),
)
.child(
div()
.id(("mcp-switch", switch as usize))
.cursor_pointer()
.child(theme.toggle(on))
.on_click(cx.listener(move |this, _, _, cx| {
this.workspace.update(cx, |workspace, cx| match switch {
Switch::Serve => workspace.set_mcp_serve(!on, cx),
Switch::Write => workspace.set_mcp_write(!on, cx),
});
cx.notify();
})),
)
.into_any_element()
}
fn mcp_address(&self, cx: &Context<Self>) -> Option<AnyElement> {
let theme = Theme::of(cx).clone();
let painter = Painter::of(cx);
let url = self.workspace.read(cx).mcp_url()?;
let copied = url.clone();
Some(
theme
.card_row(false)
.child(
div()
.flex_1()
.min_w_0()
.flex()
.flex_col()
.child(theme.row_title("Address"))
.child(
div()
.mt(px(4.))
.truncate()
.text_style(TextStyle::Subheadline)
.text_color(theme.text_muted)
.child(url),
),
)
.child(
theme
.button(
"Copy",
ButtonStyle::Ghost,
Some(Fade::new(painter, "copy-url")),
)
.id("copy-url")
.flex_none()
.on_click(move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(copied.clone()));
}),
)
.into_any_element(),
)
}
}