Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/frontend/src/components/common/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export default function Breadcrumb({ items, className }: BreadcrumbProps) {
return (
<BreadcrumbRoot className={className}>
<BreadcrumbList>
{items.length > 0 && (
<div className="flex items-center gap-1.5">
<BreadcrumbSeparator />
</div>
)}
{items.map((item, index) => {
const isLast = index === items.length - 1;

Expand All @@ -53,7 +58,7 @@ export default function Breadcrumb({ items, className }: BreadcrumbProps) {
</BreadcrumbPage>
)}
</BreadcrumbItem>
{/* {!isLast && <BreadcrumbSeparator>/</BreadcrumbSeparator>} */}
{!isLast && <BreadcrumbSeparator />}
</div>
);
})}
Expand Down
6 changes: 4 additions & 2 deletions src/frontend/src/components/common/pageLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ export default function PageLayout({
button,
betaIcon,
backTo = "",
showSeparator = true,
}: {
title: string;
description: string;
children: React.ReactNode;
button?: React.ReactNode;
betaIcon?: boolean;
backTo?: To;
showSeparator?: boolean;
}) {
const navigate = useCustomNavigate();

Expand All @@ -27,7 +29,7 @@ export default function PageLayout({
<div className="mx-auto flex w-full max-w-[1440px] flex-1 flex-col">
<div className="flex flex-col gap-4 p-6 pt-0">
<CustomBanner />
<div className="flex w-full items-center justify-between gap-4 space-y-0.5 pb-2 pt-10">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5 pb-2 pt-4">
<div className="flex w-full flex-col">
<div className="flex items-center gap-2">
{backTo && (
Expand Down Expand Up @@ -58,7 +60,7 @@ export default function PageLayout({
</div>
</div>
<div className="flex shrink-0 px-6">
<Separator className="flex" />
{showSeparator && <Separator className="flex" />}
</div>
<div className="flex flex-1 p-6 pt-7">{children}</div>
</div>
Expand Down
30 changes: 25 additions & 5 deletions src/frontend/src/components/core/appHeaderComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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);
Expand Down Expand Up @@ -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 (
<div
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/components/ui/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const BreadcrumbList = React.forwardRef<
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5 mt-6",
className,
)}
{...props}
Expand Down Expand Up @@ -66,7 +66,7 @@ const BreadcrumbPage = React.forwardRef<
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground text-white mt-4", className)}
className={cn("font-normal text-foreground text-white", className)}
{...props}
/>
));
Expand All @@ -83,7 +83,7 @@ const BreadcrumbSeparator = ({
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
{children ?? "/"}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
Expand Down
96 changes: 96 additions & 0 deletions src/frontend/src/pages/AgentMarketplacePage/DetailPage.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>;
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 (
<PageLayout
title={title}
description={""}
backTo="/agent-marketplace"
showSeparator={false}
>
<div className="flex w-full flex-col gap-4">
<div className="flex flex-col">
<Tabs defaultValue="flow" className="w-full">
<TabsList className="w-full justify-start gap-2 border-b border-border p-0">
<TabsTrigger value="flow" className="px-3 py-2 text-sm">
Flow Visualization
</TabsTrigger>
<TabsTrigger value="spec" className="px-3 py-2 text-sm">
Specification
</TabsTrigger>
</TabsList>
<TabsContent value="flow" className="mt-4 w-full">
<div className="flex h-[520px] w-full items-center justify-center rounded-lg border border-border bg-card">
<div className="flex max-w-[640px] flex-col items-center gap-3 text-center">
<ForwardedIconComponent name="GitBranch" className="h-6 w-6" />
<p className="text-sm text-muted-foreground">
Flow visualization will appear here when available.
</p>
<p className="text-xs text-muted-foreground">
Name: {state.name || "Unknown"} • Version: {state.version || "—"}
</p>
</div>
</div>
</TabsContent>
<TabsContent value="spec" className="mt-4 w-full">
<div className="flex h-[520px] w-full flex-col overflow-hidden rounded-lg border border-border">
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<div className="text-sm font-medium">YAML Specification</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => {
if (state.specYaml) {
navigator.clipboard?.writeText(state.specYaml);
}
}}
>
Copy YAML
</Button>
</div>
</div>
<div className="flex-1 overflow-auto">
<SyntaxHighlighter
language="yaml"
style={dark ? oneDark : oneLight}
customStyle={{ margin: 0, background: "transparent" }}
wrapLongLines
>
{state.specYaml || "# No specification found"}
</SyntaxHighlighter>
</div>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</PageLayout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 (
<div
className={cn(
"group relative flex h-full flex-col rounded-lg border border-[#EBE8FF] bg-white dark:bg-card px-4 py-3",
viewMode === "list" ? "cursor-default" : "cursor-default",
"group relative flex h-full flex-col rounded-lg border border-[#EBE8FF] bg-white dark:bg-card px-4 py-3 transition-shadow hover:shadow-md",
viewMode === "list" ? "cursor-pointer" : "cursor-pointer",
)}
onClick={handleCardClick}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleCardClick();
}
}}
>
{/* Header */}
<div className="mb-3 flex items-start justify-between">
Expand All @@ -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()}
>
<MoreHorizontal className="h-4 w-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[12rem]">
<DropdownMenuItem onClick={() => { /* TODO: wire Edit */ }} className="gap-2">
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); /* TODO: wire Edit */ }} className="gap-2">
<Pencil className="h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { /* TODO: wire Publish */ }} className="gap-2">
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); /* TODO: wire Publish */ }} className="gap-2">
<Upload className="h-4 w-4" />
Publish
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { /* TODO: wire Archive */ }} className="gap-2">
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); /* TODO: wire Archive */ }} className="gap-2">
<Archive className="h-4 w-4" />
Archive
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { /* TODO: wire Pricing */ }} className="gap-2">
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); /* TODO: wire Pricing */ }} className="gap-2">
<DollarSign className="h-4 w-4" />
View Pricing
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => { /* TODO: wire Delete */ }} className="gap-2 text-destructive">
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); /* TODO: wire Delete */ }} className="gap-2 text-destructive">
<Trash2 className="h-4 w-4" />
Delete
</DropdownMenuItem>
Expand Down
11 changes: 3 additions & 8 deletions src/frontend/src/pages/LoginPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 (
<Form.Root
Expand Down
Loading