feat(developer): replace agents iframe with informational landing and live agent table
LangGraph is API-only with no web UI, so the ToolEmbed iframe pattern doesn't work. Replace it with an informational landing page featuring a hero section, connection status indicator, quick-start card grid linking to docs/GitHub/examples, and a live table of registered agents fetched from the LangGraph POST /assistants/search endpoint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::ToolEmbed;
|
||||
use crate::i18n::{t, Locale};
|
||||
use crate::models::ServiceUrlsContext;
|
||||
|
||||
/// Agents page embedding the LangGraph agent builder.
|
||||
/// Agents informational landing page for LangGraph.
|
||||
///
|
||||
/// When `langgraph_url` is configured, embeds the service in an iframe
|
||||
/// with a pop-out button. Otherwise shows a "Not Configured" placeholder.
|
||||
/// Since LangGraph is API-only (no web UI), this page displays a hero section
|
||||
/// explaining its role, a connection status indicator, a card grid linking
|
||||
/// to documentation, and a live table of registered agents fetched from the
|
||||
/// LangGraph assistants API.
|
||||
#[component]
|
||||
pub fn AgentsPage() -> Element {
|
||||
let locale = use_context::<Signal<Locale>>();
|
||||
@@ -15,13 +16,209 @@ pub fn AgentsPage() -> Element {
|
||||
let l = *locale.read();
|
||||
let url = svc.read().langgraph_url.clone();
|
||||
|
||||
// Derive whether a LangGraph URL is configured
|
||||
let connected = !url.is_empty();
|
||||
// Build the API reference URL from the configured base, falling back to "#"
|
||||
let api_ref_href = if connected {
|
||||
format!("{}/docs", url)
|
||||
} else {
|
||||
"#".to_string()
|
||||
};
|
||||
|
||||
// Fetch agents from LangGraph when connected
|
||||
let agents_resource = use_resource(move || async move {
|
||||
match crate::infrastructure::langgraph::list_langgraph_agents().await {
|
||||
Ok(agents) => agents,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fetch agents: {e}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
ToolEmbed {
|
||||
url,
|
||||
title: t(l, "developer.agents_title"),
|
||||
description: t(l, "developer.agents_desc"),
|
||||
icon: "A",
|
||||
launch_label: t(l, "developer.launch_agents"),
|
||||
div { class: "agents-page",
|
||||
// -- Hero section --
|
||||
div { class: "agents-hero",
|
||||
div { class: "agents-hero-row",
|
||||
div { class: "agents-hero-icon placeholder-icon", "A" }
|
||||
h2 { class: "agents-hero-title",
|
||||
{t(l, "developer.agents_title")}
|
||||
}
|
||||
}
|
||||
p { class: "agents-hero-desc",
|
||||
{t(l, "developer.agents_desc")}
|
||||
}
|
||||
|
||||
// -- Connection status --
|
||||
if connected {
|
||||
div { class: "agents-status",
|
||||
span {
|
||||
class: "agents-status-dot agents-status-dot--on",
|
||||
}
|
||||
span { {t(l, "developer.agents_status_connected")} }
|
||||
code { class: "agents-status-url", {url.clone()} }
|
||||
}
|
||||
} else {
|
||||
div { class: "agents-status",
|
||||
span {
|
||||
class: "agents-status-dot agents-status-dot--off",
|
||||
}
|
||||
span { {t(l, "developer.agents_status_not_connected")} }
|
||||
span { class: "agents-status-hint",
|
||||
{t(l, "developer.agents_config_hint")}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Running Agents table --
|
||||
div { class: "agents-table-section",
|
||||
h3 { class: "agents-section-title",
|
||||
{t(l, "developer.agents_running_title")}
|
||||
}
|
||||
|
||||
match agents_resource.read().as_ref() {
|
||||
None => {
|
||||
rsx! {
|
||||
p { class: "agents-table-loading",
|
||||
{t(l, "common.loading")}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(agents) if agents.is_empty() => {
|
||||
rsx! {
|
||||
p { class: "agents-table-empty",
|
||||
{t(l, "developer.agents_none")}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(agents) => {
|
||||
rsx! {
|
||||
div { class: "agents-table-wrap",
|
||||
table { class: "agents-table",
|
||||
thead {
|
||||
tr {
|
||||
th { {t(l, "developer.agents_col_name")} }
|
||||
th { {t(l, "developer.agents_col_id")} }
|
||||
th { {t(l, "developer.agents_col_description")} }
|
||||
th { {t(l, "developer.agents_col_status")} }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for agent in agents.iter() {
|
||||
tr { key: "{agent.id}",
|
||||
td { class: "agents-cell-name",
|
||||
{agent.name.clone()}
|
||||
}
|
||||
td {
|
||||
code { class: "agents-cell-id",
|
||||
{agent.id.clone()}
|
||||
}
|
||||
}
|
||||
td { class: "agents-cell-desc",
|
||||
if agent.description.is_empty() {
|
||||
span { class: "agents-cell-none", "--" }
|
||||
} else {
|
||||
{agent.description.clone()}
|
||||
}
|
||||
}
|
||||
td {
|
||||
span { class: "agents-badge agents-badge--active",
|
||||
{agent.status.clone()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Quick Start card grid --
|
||||
h3 { class: "agents-section-title",
|
||||
{t(l, "developer.agents_quick_start")}
|
||||
}
|
||||
|
||||
div { class: "agents-grid",
|
||||
// Documentation
|
||||
a {
|
||||
class: "agents-card",
|
||||
href: "https://langchain-ai.github.io/langgraph/",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
div { class: "agents-card-icon placeholder-icon", "D" }
|
||||
div { class: "agents-card-title",
|
||||
{t(l, "developer.agents_docs")}
|
||||
}
|
||||
div { class: "agents-card-desc",
|
||||
{t(l, "developer.agents_docs_desc")}
|
||||
}
|
||||
}
|
||||
|
||||
// Getting Started
|
||||
a {
|
||||
class: "agents-card",
|
||||
href: "https://langchain-ai.github.io/langgraph/tutorials/introduction/",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
div { class: "agents-card-icon placeholder-icon", "G" }
|
||||
div { class: "agents-card-title",
|
||||
{t(l, "developer.agents_getting_started")}
|
||||
}
|
||||
div { class: "agents-card-desc",
|
||||
{t(l, "developer.agents_getting_started_desc")}
|
||||
}
|
||||
}
|
||||
|
||||
// GitHub
|
||||
a {
|
||||
class: "agents-card",
|
||||
href: "https://github.com/langchain-ai/langgraph",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
div { class: "agents-card-icon placeholder-icon", "H" }
|
||||
div { class: "agents-card-title",
|
||||
{t(l, "developer.agents_github")}
|
||||
}
|
||||
div { class: "agents-card-desc",
|
||||
{t(l, "developer.agents_github_desc")}
|
||||
}
|
||||
}
|
||||
|
||||
// Examples
|
||||
a {
|
||||
class: "agents-card",
|
||||
href: "https://github.com/langchain-ai/langgraph/tree/main/examples",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
div { class: "agents-card-icon placeholder-icon", "E" }
|
||||
div { class: "agents-card-title",
|
||||
{t(l, "developer.agents_examples")}
|
||||
}
|
||||
div { class: "agents-card-desc",
|
||||
{t(l, "developer.agents_examples_desc")}
|
||||
}
|
||||
}
|
||||
|
||||
// API Reference (disabled when URL is empty)
|
||||
a {
|
||||
class: if connected { "agents-card" } else { "agents-card agents-card--disabled" },
|
||||
href: "{api_ref_href}",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
div { class: "agents-card-icon placeholder-icon", "R" }
|
||||
div { class: "agents-card-title",
|
||||
{t(l, "developer.agents_api_ref")}
|
||||
}
|
||||
div { class: "agents-card-desc",
|
||||
{t(l, "developer.agents_api_ref_desc")}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user