diff --git a/src/frontend/src/components/common/Breadcrumb.tsx b/src/frontend/src/components/common/Breadcrumb.tsx index 378587874ff0..0563aa54d7be 100644 --- a/src/frontend/src/components/common/Breadcrumb.tsx +++ b/src/frontend/src/components/common/Breadcrumb.tsx @@ -29,6 +29,11 @@ export default function Breadcrumb({ items, className }: BreadcrumbProps) { return ( + {items.length > 0 && ( +
+ +
+ )} {items.map((item, index) => { const isLast = index === items.length - 1; @@ -53,7 +58,7 @@ export default function Breadcrumb({ items, className }: BreadcrumbProps) { )} - {/* {!isLast && /} */} + {!isLast && } ); })} diff --git a/src/frontend/src/components/common/pageLayout/index.tsx b/src/frontend/src/components/common/pageLayout/index.tsx index a077eadb31c1..05accdc7deb2 100644 --- a/src/frontend/src/components/common/pageLayout/index.tsx +++ b/src/frontend/src/components/common/pageLayout/index.tsx @@ -12,6 +12,7 @@ export default function PageLayout({ button, betaIcon, backTo = "", + showSeparator = true, }: { title: string; description: string; @@ -19,6 +20,7 @@ export default function PageLayout({ button?: React.ReactNode; betaIcon?: boolean; backTo?: To; + showSeparator?: boolean; }) { const navigate = useCustomNavigate(); @@ -27,7 +29,7 @@ export default function PageLayout({
-
+
{backTo && ( @@ -58,7 +60,7 @@ export default function PageLayout({
- + {showSeparator && }
{children}
diff --git a/src/frontend/src/components/core/appHeaderComponent/index.tsx b/src/frontend/src/components/core/appHeaderComponent/index.tsx index 709704a33e3d..cfe1e11883e7 100644 --- a/src/frontend/src/components/core/appHeaderComponent/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/index.tsx @@ -17,6 +17,7 @@ import { useSidebar } from "@/contexts/sidebarContext"; import { PanelLeft, Moon, Sun } from "lucide-react"; import FlowMenu from "./components/FlowMenu"; import Breadcrumb from "@/components/common/Breadcrumb"; +import { useLocation } from "react-router-dom"; import { useDarkStore } from "@/stores/darkStore"; // import AutonomizeIcon from "@/icons/Autonomize"; @@ -30,6 +31,7 @@ export default function AppHeader(): JSX.Element { const dark = useDarkStore((state) => state.dark); const setDark = useDarkStore((state) => state.setDark); useTheme(); + const location = useLocation(); const toggleTheme = () => { setDark(!dark); @@ -60,11 +62,29 @@ export default function AppHeader(): JSX.Element { // : "hidden"; // }; - // Breadcrumb navigation - const breadcrumbItems = [ - { label: "/" }, - { label: "AI Studio", href: "/agent-builder" }, - ]; + // Breadcrumb navigation (route-aware) + const breadcrumbItems = (() => { + const pathname = location.pathname || ""; + const state = location.state as { name?: string; fileName?: string } | undefined; + + // AI Studio (builder) hierarchy + if (pathname.startsWith("/agent-builder")) { + return [{ label: "AI Studio", href: "/agent-builder" }]; + } + + // Agent Marketplace hierarchy + if (pathname.startsWith("/agent-marketplace")) { + const items = [{ label: "Agent Marketplace", href: "/agent-marketplace" }]; + if (pathname.startsWith("/agent-marketplace/detail")) { + const current = state?.name || state?.fileName || "Details"; + items.push({ label: current, href: '' }); + } + return items; + } + + // Default: no breadcrumbs beyond logo + return []; + })(); return (
)); @@ -83,7 +83,7 @@ const BreadcrumbSeparator = ({ className={cn("[&>svg]:size-3.5", className)} {...props} > - {children ?? } + {children ?? "/"} ); BreadcrumbSeparator.displayName = "BreadcrumbSeparator"; diff --git a/src/frontend/src/pages/AgentMarketplacePage/DetailPage.tsx b/src/frontend/src/pages/AgentMarketplacePage/DetailPage.tsx new file mode 100644 index 000000000000..65f31da0e156 --- /dev/null +++ b/src/frontend/src/pages/AgentMarketplacePage/DetailPage.tsx @@ -0,0 +1,96 @@ +import { useLocation } from "react-router-dom"; +import PageLayout from "@/components/common/pageLayout"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { oneDark, oneLight } from "react-syntax-highlighter/dist/cjs/styles/prism"; +import { useDarkStore } from "@/stores/darkStore"; +import { Button } from "@/components/ui/button"; +import ForwardedIconComponent from "@/components/common/genericIconComponent"; + +type MarketplaceDetailState = { + name?: string; + description?: string; + domain?: string; + version?: string; + specYaml?: string; + spec?: Record; + fileName?: string; + folderName?: string; + status?: string; +}; + +export default function AgentMarketplaceDetailPage() { + const location = useLocation(); + const dark = useDarkStore((state) => state.dark); + const state = (location.state || {}) as MarketplaceDetailState; + + const title = state.name || state.fileName || "Agent Details"; + const description = state.description || "Explore details and specification."; + + return ( + +
+
+ + + + Flow Visualization + + + Specification + + + +
+
+ +

+ Flow visualization will appear here when available. +

+

+ Name: {state.name || "Unknown"} • Version: {state.version || "—"} +

+
+
+
+ +
+
+
YAML Specification
+
+ +
+
+
+ + {state.specYaml || "# No specification found"} + +
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/frontend/src/pages/AgentMarketplacePage/components/MarketplaceAgentCard.tsx b/src/frontend/src/pages/AgentMarketplacePage/components/MarketplaceAgentCard.tsx index 651c2586ae8b..2fa61c802593 100644 --- a/src/frontend/src/pages/AgentMarketplacePage/components/MarketplaceAgentCard.tsx +++ b/src/frontend/src/pages/AgentMarketplacePage/components/MarketplaceAgentCard.tsx @@ -11,6 +11,8 @@ import { MoreHorizontal, Pencil, Upload, Archive, DollarSign, Trash2 } from "luc import { cn } from "@/utils/utils"; import type { AgentSpecItem } from "@/controllers/API/queries/agent-marketplace/use-get-agent-marketplace"; import ShadTooltip from "@/components/common/shadTooltipComponent"; +import { useMemo } from "react"; +import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; interface MarketplaceAgentCardProps { item: AgentSpecItem; @@ -25,12 +27,91 @@ export default function MarketplaceAgentCard({ item, viewMode = "grid" }: Market const domain = item.spec?.subDomain ?? item.spec?.domain ?? ""; const version = item.spec?.version ?? ""; + const navigate = useCustomNavigate(); + + // Lightweight JSON → YAML conversion implemented locally + const jsonToYaml = (value: any, indent = 0): string => { + const spacer = " ".repeat(indent); + const nextIndent = indent + 2; + + const formatScalar = (v: any): string => { + if (v === null || v === undefined) return "null"; + const t = typeof v; + if (t === "string") return JSON.stringify(v); // safe quoting + if (t === "number") return Number.isFinite(v) ? String(v) : JSON.stringify(v); + if (t === "boolean") return v ? "true" : "false"; + return JSON.stringify(v); + }; + + if (Array.isArray(value)) { + if (value.length === 0) return "[]"; + return value + .map((item) => { + if (item && typeof item === "object") { + const nested = jsonToYaml(item, nextIndent); + return `${spacer}- ${nested.startsWith("\n") ? nested.substring(1) : `\n${nested}`}`; + } + return `${spacer}- ${formatScalar(item)}`; + }) + .join("\n"); + } + + if (value && typeof value === "object") { + const keys = Object.keys(value); + if (keys.length === 0) return "{}"; + return keys + .map((key) => { + const val = (value as any)[key]; + if (val && typeof val === "object") { + const nested = jsonToYaml(val, nextIndent); + if (Array.isArray(val)) { + // arrays inline if empty, otherwise block + return `${spacer}${key}: ${nested.includes("\n") ? `\n${nested}` : nested}`; + } + return `${spacer}${key}:\n${nested}`; + } + return `${spacer}${key}: ${formatScalar(val)}`; + }) + .join("\n"); + } + + // scalars + return `${spacer}${formatScalar(value)}`; + }; + + const specYaml = useMemo(() => jsonToYaml(item.spec ?? {}), [item.spec]); + + const handleCardClick = () => { + navigate("/agent-marketplace/detail", { + state: { + name, + description, + domain, + version, + specYaml, + spec: item.spec ?? {}, + fileName: item.file_name, + folderName: item.folder_name, + status, + }, + }); + }; + return (
{ + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleCardClick(); + } + }} > {/* Header */}
@@ -57,29 +138,30 @@ export default function MarketplaceAgentCard({ item, viewMode = "grid" }: Market variant="ghost" size="icon" className="h-8 w-8" + onClick={(e) => e.stopPropagation()} > - { /* TODO: wire Edit */ }} className="gap-2"> + { e.stopPropagation(); /* TODO: wire Edit */ }} className="gap-2"> Edit - { /* TODO: wire Publish */ }} className="gap-2"> + { e.stopPropagation(); /* TODO: wire Publish */ }} className="gap-2"> Publish - { /* TODO: wire Archive */ }} className="gap-2"> + { e.stopPropagation(); /* TODO: wire Archive */ }} className="gap-2"> Archive - { /* TODO: wire Pricing */ }} className="gap-2"> + { e.stopPropagation(); /* TODO: wire Pricing */ }} className="gap-2"> View Pricing - { /* TODO: wire Delete */ }} className="gap-2 text-destructive"> + { e.stopPropagation(); /* TODO: wire Delete */ }} className="gap-2 text-destructive"> Delete diff --git a/src/frontend/src/pages/LoginPage/index.tsx b/src/frontend/src/pages/LoginPage/index.tsx index d48c9b042363..b9d83db50865 100644 --- a/src/frontend/src/pages/LoginPage/index.tsx +++ b/src/frontend/src/pages/LoginPage/index.tsx @@ -57,9 +57,9 @@ export default function LoginPage(): JSX.Element | null { }, []); // When Keycloak is enabled, render nothing; effect above will redirect - if (envConfig.keycloakEnabled) { - return null; - } + // if (envConfig.keycloakEnabled) { + // return null; + // } function signIn() { const user: LoginType = { @@ -80,11 +80,6 @@ export default function LoginPage(): JSX.Element | null { }); } - // // Render nothing when Keycloak is enabled; the effect above will redirect - // if (envConfig.keycloakEnabled) { - // return null; - // } - // Fallback: traditional login UI when Keycloak is disabled return ( + } /> }>