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
12 changes: 7 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ Consult these guides before working on related tasks:
- Agent architecture is derived from `bots/**` and `botcomponents/**`; component
ownership must be resolved per agent rather than applying every solution
component to every bot.
- Agent previews use a deterministic hub-and-spoke layout: primary agents are
centered, owned agents/tools/skills share radial rings, and straight edges
attach to the nearest node side. Node color is presentation-only and does not
alter the imported topology.
- Detail pages render the imported workflow and agent topology. Gallery cards
instead show a fixed asset inventory for workflows, agents, tools, MCP
servers, and skills so dense graphs are not compressed into unreadable
thumbnails.
- The browser receives compact allowlisted graph data. Raw schemas, icons,
environment connection identifiers, and source blobs stay out of generated
content.
- Download ZIPs are deterministic rebuilds of the `solution/` contents at archive
root; gallery sidecars are excluded.
root; gallery sidecars are excluded. Downloads stay locked until the visitor
acknowledges that community solutions should be reviewed and imported only
from a trusted source.
94 changes: 94 additions & 0 deletions src/components/AssetSummaryPreview.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
import type { GraphData } from "../lib/schema";
import { isMcpNode, MCP_ICON_PATHS } from "../lib/graphVisuals";

interface Props {
agentCount: number;
workflowCount: number;
agentGraph: GraphData;
label?: string;
}

const {
agentCount,
workflowCount,
agentGraph,
label = "Solution assets",
} = Astro.props;

const capabilityNodes = agentGraph.nodes.filter((node) => node.type === "tool" || node.type === "connector");
const mcpCount = capabilityNodes.filter(isMcpNode).length;
const toolCount = capabilityNodes.length - mcpCount;
const skillCount = agentGraph.nodes.filter((node) => node.type === "skill").length;
const assets = [
{ kind: "workflow", label: "Workflows", count: workflowCount, color: "var(--graph-action)" },
{ kind: "agent", label: "Agents", count: agentCount, color: "var(--graph-ai)" },
{ kind: "tool", label: "Tools", count: toolCount, color: "var(--graph-tool)" },
{ kind: "mcp", label: "MCP", count: mcpCount, color: "var(--graph-mcp)" },
{ kind: "skill", label: "Skills", count: skillCount, color: "var(--graph-skill)" },
] as const;
const summary = assets.map((asset) => `${asset.count} ${asset.label.toLowerCase()}`).join(", ");
---

<div role="img" aria-label={`${label}: ${summary}`} class="flex h-full items-center justify-center gap-2 sm:gap-3">
{
assets.map((asset) => (
<div class:list={["flex min-w-0 flex-1 flex-col items-center", asset.count === 0 && "opacity-35"]}>
<span
class="relative grid h-11 w-11 place-items-center rounded-xl border bg-surface shadow-sm"
style={`border-color:color-mix(in srgb, ${asset.color} 65%, var(--cp-border));color:${asset.color}`}
>
{asset.kind === "workflow" && (
<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="h-7 w-7">
<rect x="3" y="3" width="10" height="10" rx="3"></rect>
<path d="m12 12 8 8"></path>
<path d="M16 20h4v-4"></path>
<rect x="19" y="19" width="10" height="10" rx="3"></rect>
</svg>
)}
{asset.kind === "agent" && (
<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" class="h-7 w-7">
<path d="M16 7V4"></path>
<rect x="7" y="8" width="18" height="16" rx="6"></rect>
<rect x="11" y="13" width="10" height="6" rx="3" fill="currentColor" stroke="none"></rect>
<circle cx="14" cy="16" r="1" fill="var(--cp-surface)" stroke="none"></circle>
<circle cx="18" cy="16" r="1" fill="var(--cp-surface)" stroke="none"></circle>
<path d="M5 14v5M27 14v5"></path>
<path d="m24 5 1 2 2 1-2 1-1 2-1-2-2-1 2-1 1-2Z" fill="currentColor" stroke="none"></path>
</svg>
)}
{asset.kind === "tool" && (
<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" class="h-7 w-7">
<path d="M11 5v7M21 5v7"></path>
<path d="M8 11h16v3a8 8 0 0 1-16 0v-3Z"></path>
<path d="M16 22v5"></path>
</svg>
)}
{asset.kind === "mcp" && (
<svg viewBox="0 0 195 195" fill="none" aria-hidden="true" class="h-7 w-7">
{MCP_ICON_PATHS.map((path) => (
<path d={path} stroke="currentColor" stroke-width="12" stroke-linecap="round"></path>
))}
</svg>
)}
{asset.kind === "skill" && (
<svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true" class="h-7 w-7">
<path d="m16 4 10 6v12l-10 6-10-6V10l10-6Z"></path>
<path d="m16 10 1.5 4.5L22 16l-4.5 1.5L16 22l-1.5-4.5L10 16l4.5-1.5L16 10Z" fill="currentColor" stroke="none"></path>
</svg>
)}
{asset.count > 0 && (
<span
class="absolute -right-1.5 -top-1.5 grid min-h-5 min-w-5 place-items-center rounded-full border border-surface bg-ink px-1 text-[10px] font-bold leading-none text-page"
>
{asset.count}
</span>
)}
</span>
<span class="mt-2 truncate font-mono text-[9px] font-bold uppercase tracking-[0.08em] text-soft">
{asset.label}
</span>
</div>
))
}
</div>
13 changes: 6 additions & 7 deletions src/components/SolutionCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { CollectionEntry } from "astro:content";
import { authorKey, solutionMakeup } from "../lib/schema";
import { withBase } from "../lib/url";
import TopologyPreview from "./TopologyPreview.astro";
import AssetSummaryPreview from "./AssetSummaryPreview.astro";

interface Props {
solution: CollectionEntry<"solutions">;
Expand All @@ -11,7 +11,6 @@ interface Props {

const { solution, compact = false } = Astro.props;
const data = solution.data;
const graph = data.workflows[0]?.graph ?? data.agentGraph;
const makeup = solutionMakeup(data.agentCount, data.workflowCount);
const isDemo = data.tags.includes("demo");
const buildingBlocks = [
Expand All @@ -27,11 +26,11 @@ const buildingBlocks = [
<div class:list={["studio-canvas relative m-3 mb-0 overflow-hidden rounded-xl border border-border/70 px-3 py-2 text-muted", compact ? "h-28" : "h-36"]}>
<div class="studio-grid absolute inset-0 opacity-70"></div>
<div class="relative h-full transition-transform duration-300 group-hover:scale-[1.02]">
<TopologyPreview
graph={graph}
label={`${data.name} topology`}
variant={data.workflowCount ? "workflow" : "agent"}
compact
<AssetSummaryPreview
agentCount={data.agentCount}
workflowCount={data.workflowCount}
agentGraph={data.agentGraph}
label={`${data.name} asset inventory`}
/>
</div>
</div>
Expand Down
Loading
Loading