Files
Ely-GPUI-Components/examples/gallery/pages/agent/environment.rs
T
ZacharyZhang-NY cbdcf85cec T24e: Agent, environment
- SandboxStatus: a dot and a word (a ring while it starts or works), what it
  uses of what it has as meters, and how long it has run; the header wraps
- McpServerList over lists::ListItem: a dot per state, what it runs or why it
  failed, its tools once connected, and a switch
- ToolRegistry: tools grouped by source, found by every word of the owner's
  query, each Allow, Ask or Deny; rows wrap at 280px
- MemoryPanel: a line to keep a memory, Enter or Remember, blank words kept
  out; each memory with its source, when, and Forget
- CheckpointList over data_display::Timeline, newest first, the current point
  marked, Rewind on the rest
- gallery: every control wired to state
2026-09-26 11:00:15 -04:00

282 lines
8.9 KiB
Rust

use std::time::Duration;
use ely_gpui_component::{
agent::{
Checkpoint, CheckpointList, McpServer, McpServerList, Memory, MemoryPanel, RegisteredTool,
SandboxState, SandboxStatus, ServerState, ToolAccess, ToolRegistry,
},
forms::{SearchInput, TextInput},
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use jiff::{Timestamp, ToSpan};
use crate::{
probe::probe,
ui::{keep, section, set},
};
pub fn sandbox_section(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
section(
"SandboxStatus",
"Where an agent's sandbox stands: a dot and a word, what it uses of what it has, and how long it has run. A dot rings while it starts or works.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_start()
.gap_4()
.child(
div().w(px(320.)).child(
SandboxStatus::new("agent-sandbox", "python 3.12 · 2 vCPU", SandboxState::Ready)
.cpu(0.18)
.memory(1_240_000_000, 4_294_967_296)
.uptime(Duration::from_secs(47 * 60)),
),
)
.child(
div().w(px(280.)).child(
SandboxStatus::new("agent-sandbox-starting", "node 22 · 1 vCPU", SandboxState::Starting),
),
),
)
}
fn servers() -> Vec<McpServer> {
[
(
"files",
"Filesystem",
"npx @modelcontextprotocol/server-filesystem ~/ely",
ServerState::Connected,
11,
),
(
"github",
"GitHub",
"https://api.githubcopilot.com/mcp",
ServerState::Connected,
38,
),
(
"figma",
"Figma",
"Connecting to the desktop app…",
ServerState::Connecting,
0,
),
(
"postgres",
"Postgres",
"Refused: password authentication failed",
ServerState::Failed,
0,
),
(
"linear",
"Linear",
"https://mcp.linear.app/sse",
ServerState::Off,
24,
),
]
.into_iter()
.map(|(key, name, detail, state, tools)| McpServer {
key: key.into(),
name: name.into(),
detail: detail.into(),
state,
tools,
})
.collect()
}
fn tools() -> Vec<RegisteredTool> {
[
(
"read_file",
"Filesystem",
"Reads a file under the project",
ToolAccess::Allow,
),
(
"write_file",
"Filesystem",
"Writes or replaces a file",
ToolAccess::Ask,
),
(
"run_command",
"Filesystem",
"Runs a shell command in the sandbox",
ToolAccess::Ask,
),
(
"create_issue",
"GitHub",
"Opens an issue in a repository",
ToolAccess::Ask,
),
(
"merge_pull_request",
"GitHub",
"Merges a pull request",
ToolAccess::Deny,
),
(
"web_search",
"Built in",
"Searches the web and cites sources",
ToolAccess::Allow,
),
]
.into_iter()
.map(|(name, source, description, access)| RegisteredTool {
name: name.into(),
source: source.into(),
description: description.into(),
access,
})
.collect()
}
pub fn connectors_section(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let list = keep("agent-servers", servers, window, cx);
let registry = keep("agent-tools", tools, window, cx);
let query = window.use_keyed_state("agent-find-tool", cx, |window, cx| {
TextInput::new(window, cx).placeholder("Find a tool")
});
let (now_list, now_tools, now_query) = (
list.read(cx).clone(),
registry.read(cx).clone(),
query.read(cx).text().to_string(),
);
section(
"MCPServerList / ConnectorList / ToolRegistry Panel",
"Servers and connectors the agent can reach, each with where it stands and a switch; and every tool they bring, found by name, each allowed, asked for first, or denied.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_start()
.gap_6()
.child(
div().w(px(420.)).child(McpServerList::new("agent-mcp", now_list).on_toggle(move |key, on, _, cx| {
let mut next = list.read(cx).clone();
let server = next.iter_mut().find(|server| &server.key == key).expect("a listed server");
server.state = if on { ServerState::Connected } else { ServerState::Off };
set(&list, next, cx)
})),
)
.child(
div()
.w(px(460.))
.flex()
.flex_col()
.gap_3()
.child(SearchInput::new("agent-find-tool", &query))
.child(
ToolRegistry::new("agent-registry", now_tools)
.query(now_query)
.on_access(move |ix, access, _, cx| {
let mut next = registry.read(cx).clone();
next[ix].access = access;
set(&registry, next, cx)
}),
),
),
)
}
pub fn memory_section(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let now = Timestamp::now();
let kept = keep(
"agent-memories",
|| {
vec![
Memory {
key: "tone".into(),
text: "Prefers plain words and short sentences in notes.".into(),
source: Some("Design review".into()),
at: now - 72.hours(),
},
Memory {
key: "lift".into(),
text: "Dark themes lift accents by 18%; surfaces by 4%.".into(),
source: Some("Lift the dark theme".into()),
at: now - 5.hours(),
},
]
},
window,
cx,
);
let field = window.use_keyed_state("agent-memory-field", cx, |window, cx| {
TextInput::new(window, cx).placeholder("Something to keep")
});
let points = keep(
"agent-checkpoints",
|| {
[
("Read the palette", 0, 40),
("Measured contrast", 0, 32),
("Lifted the accents", 2, 18),
("Ran the tests", 2, 9),
("Fixed the dark lift", 3, 2),
]
.into_iter()
.map(|(label, files, minutes)| Checkpoint {
key: SharedString::from(label),
label: label.into(),
at: now - (minutes as i64).minutes(),
files,
})
.collect::<Vec<_>>()
},
window,
cx,
);
let at = keep("agent-checkpoint", || 4_usize, window, cx);
let (now_kept, now_points, now_at) =
(kept.read(cx).clone(), points.read(cx).clone(), *at.read(cx));
let (added, forgot) = (kept.clone(), kept);
section(
"MemoryPanel / CheckpointList / Rewind",
"What an agent keeps between conversations, a line to add to it and a way to forget; and the points it can go back to, newest first, the one it stands on marked.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_start()
.gap_6()
.child(
div().w(px(400.)).child(
MemoryPanel::new("agent-memory", &field, now_kept)
.on_add(move |text, _, cx| {
let mut next = added.read(cx).clone();
let key = SharedString::from(format!("kept-{}", next.len()));
next.insert(0, Memory { key, text: text.to_string().into(), source: None, at: Timestamp::now() });
set(&added, next, cx)
})
.on_forget(move |key, _, cx| {
let mut next = forgot.read(cx).clone();
next.retain(|memory| &memory.key != key);
set(&forgot, next, cx)
}),
),
)
.child(probe(
"agent-checkpoints",
div().w(px(400.)).child(
CheckpointList::new("agent-points", now_points, now_at)
.on_rewind(move |ix, _, cx| set(&at, ix, cx)),
),
)),
)
}