mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 04:42:10 +03:00
feat(agent-skills): expand SkillsConceptCard with 5-row conceptual comparison (GAP-A)
This commit is contained in:
@@ -7304,6 +7304,35 @@
|
||||
"title": "Omni Skills — Inbound",
|
||||
"description": "Omni Skills are sandbox tools that OmniRoute injects into the model's context on every request. They are executed by OmniRoute, not read by the agent.",
|
||||
"crossLinkLabel": "Understand the difference →"
|
||||
},
|
||||
"comparison": {
|
||||
"colAgent": "Agent Skills",
|
||||
"colOmni": "Omni Skills",
|
||||
"whatIs": {
|
||||
"label": "What it is",
|
||||
"agent": "Machine-readable SKILL.md that teaches an external agent to operate OmniRoute",
|
||||
"omni": "Executable tools that OmniRoute injects as tools into LLM requests"
|
||||
},
|
||||
"direction": {
|
||||
"label": "Direction",
|
||||
"agent": "Outbound — external agent learns to control OmniRoute",
|
||||
"omni": "Inbound — OmniRoute gives tools to models passing through it"
|
||||
},
|
||||
"executor": {
|
||||
"label": "Executor",
|
||||
"agent": "External agent (reads markdown → acts via API/CLI)",
|
||||
"omni": "OmniRoute itself (intercepts tool_calls, Docker sandbox)"
|
||||
},
|
||||
"storage": {
|
||||
"label": "Storage",
|
||||
"agent": "Dynamic catalog (/api/agent-skills) → SKILL.md on GitHub",
|
||||
"omni": "SQLite (SkillsMP / skills.sh / local)"
|
||||
},
|
||||
"tagline": {
|
||||
"label": "Tagline",
|
||||
"agent": "Teach your agent to use OmniRoute",
|
||||
"omni": "Give executable tools to OmniRoute models"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
||||
@@ -7294,6 +7294,35 @@
|
||||
"title": "Omni Skills — Entrada",
|
||||
"description": "Omni Skills são ferramentas sandbox que o OmniRoute injeta no contexto do modelo a cada solicitação. São executadas pelo OmniRoute, não lidas pelo agente.",
|
||||
"crossLinkLabel": "Entenda a diferença →"
|
||||
},
|
||||
"comparison": {
|
||||
"colAgent": "Agent Skills",
|
||||
"colOmni": "Omni Skills",
|
||||
"whatIs": {
|
||||
"label": "O que é",
|
||||
"agent": "Documentação (SKILL.md) que ensina um agente externo a operar o OmniRoute",
|
||||
"omni": "Ferramentas executáveis que o Omni injeta como tools nos requests LLM"
|
||||
},
|
||||
"direction": {
|
||||
"label": "Direção",
|
||||
"agent": "Outbound — externo aprende a controlar o Omni",
|
||||
"omni": "Inbound — Omni dá tools aos modelos que passam por ele"
|
||||
},
|
||||
"executor": {
|
||||
"label": "Executor",
|
||||
"agent": "Agente externo (lê markdown → age via API/CLI)",
|
||||
"omni": "O próprio Omni (intercepta tool_calls, sandbox Docker)"
|
||||
},
|
||||
"storage": {
|
||||
"label": "Storage",
|
||||
"agent": "Catálogo dinâmico (/api/agent-skills) → SKILL.md no GitHub",
|
||||
"omni": "SQLite (SkillsMP / skills.sh / local)"
|
||||
},
|
||||
"tagline": {
|
||||
"label": "Frase",
|
||||
"agent": "Ensine seu agente a usar o OmniRoute",
|
||||
"omni": "Dê ferramentas executáveis aos modelos do OmniRoute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
|
||||
@@ -8,13 +8,15 @@ export interface SkillsConceptCardProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const COMPARISON_ROWS = ["whatIs", "direction", "executor", "storage", "tagline"] as const;
|
||||
type ComparisonRow = (typeof COMPARISON_ROWS)[number];
|
||||
|
||||
export function SkillsConceptCard({ variant, className = "" }: SkillsConceptCardProps): JSX.Element {
|
||||
const t = useTranslations("agentSkills");
|
||||
|
||||
const crossLinkHref = variant === "agent" ? "/dashboard/omni-skills" : "/dashboard/agent-skills";
|
||||
|
||||
const title = t(`conceptCard.${variant}.title`);
|
||||
const description = t(`conceptCard.${variant}.description`);
|
||||
const crossLinkLabel = t(`conceptCard.${variant}.crossLinkLabel`);
|
||||
|
||||
const agentIcon = "share";
|
||||
@@ -23,22 +25,58 @@ export function SkillsConceptCard({ variant, className = "" }: SkillsConceptCard
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-start gap-4 rounded-xl border border-black/10 dark:border-white/10 bg-bg-subtle p-4 ${className}`}
|
||||
data-testid={`skills-concept-card-${variant}`}
|
||||
className={`rounded-xl border border-black/10 dark:border-white/10 bg-bg-subtle p-4 ${className}`}
|
||||
>
|
||||
<div className="flex items-center justify-center size-10 rounded-lg bg-primary/10 shrink-0">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">{icon}</span>
|
||||
{/* Header row */}
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="flex items-center justify-center size-10 rounded-lg bg-primary/10 shrink-0">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">{icon}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-text-main">{title}</h3>
|
||||
</div>
|
||||
<Link
|
||||
href={crossLinkHref}
|
||||
className="shrink-0 flex items-center gap-1 text-xs text-primary hover:text-primary/80 transition-colors whitespace-nowrap"
|
||||
>
|
||||
{crossLinkLabel}
|
||||
<span className="material-symbols-outlined text-[14px]">arrow_forward</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-text-main">{title}</h3>
|
||||
<p className="mt-0.5 text-xs text-text-muted leading-relaxed">{description}</p>
|
||||
|
||||
{/* Comparison table */}
|
||||
<div className="rounded-lg border border-black/5 dark:border-white/5 overflow-hidden">
|
||||
{/* Column headers */}
|
||||
<div className="grid grid-cols-3 bg-bg-subtle border-b border-black/5 dark:border-white/5">
|
||||
<div className="px-3 py-2" />
|
||||
<div className="px-3 py-2 text-xs font-semibold text-text-muted border-l border-black/5 dark:border-white/5">
|
||||
{t("conceptCard.comparison.colAgent")}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-semibold text-text-muted border-l border-black/5 dark:border-white/5">
|
||||
{t("conceptCard.comparison.colOmni")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rows */}
|
||||
{COMPARISON_ROWS.map((row: ComparisonRow, i) => (
|
||||
<div
|
||||
key={row}
|
||||
data-testid={`comparison-row-${row}`}
|
||||
className={`grid grid-cols-3 ${i < COMPARISON_ROWS.length - 1 ? "border-b border-black/5 dark:border-white/5" : ""}`}
|
||||
>
|
||||
<div className="px-3 py-2 text-xs font-medium text-text-muted bg-bg-subtle/50">
|
||||
{t(`conceptCard.comparison.${row}.label`)}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs text-text-main border-l border-black/5 dark:border-white/5">
|
||||
{t(`conceptCard.comparison.${row}.agent`)}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs text-text-main border-l border-black/5 dark:border-white/5">
|
||||
{t(`conceptCard.comparison.${row}.omni`)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href={crossLinkHref}
|
||||
className="shrink-0 flex items-center gap-1 text-xs text-primary hover:text-primary/80 transition-colors whitespace-nowrap"
|
||||
>
|
||||
{crossLinkLabel}
|
||||
<span className="material-symbols-outlined text-[14px]">arrow_forward</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ describe("SkillsConceptCard", () => {
|
||||
const text = container.textContent ?? "";
|
||||
// The i18n mock returns the key itself, so these keys should appear.
|
||||
expect(text).toContain("conceptCard.agent.title");
|
||||
expect(text).toContain("conceptCard.agent.description");
|
||||
expect(text).toContain("conceptCard.agent.crossLinkLabel");
|
||||
});
|
||||
|
||||
@@ -81,10 +80,58 @@ describe("SkillsConceptCard", () => {
|
||||
|
||||
const text = container.textContent ?? "";
|
||||
expect(text).toContain("conceptCard.omni.title");
|
||||
expect(text).toContain("conceptCard.omni.description");
|
||||
expect(text).toContain("conceptCard.omni.crossLinkLabel");
|
||||
});
|
||||
|
||||
it("agent variant renders 5 comparison rows (whatIs, direction, executor, storage, tagline)", async () => {
|
||||
const { SkillsConceptCard } = await import(
|
||||
"../../src/shared/components/SkillsConceptCard.tsx"
|
||||
);
|
||||
const container = makeContainer();
|
||||
const root = createRoot(container);
|
||||
await act(async () => {
|
||||
root.render(<SkillsConceptCard variant="agent" />);
|
||||
});
|
||||
|
||||
const rows = container.querySelectorAll("[data-testid^='comparison-row-']");
|
||||
expect(rows.length).toBe(5);
|
||||
|
||||
const rowIds = Array.from(rows).map((r) => r.getAttribute("data-testid"));
|
||||
expect(rowIds).toContain("comparison-row-whatIs");
|
||||
expect(rowIds).toContain("comparison-row-direction");
|
||||
expect(rowIds).toContain("comparison-row-executor");
|
||||
expect(rowIds).toContain("comparison-row-storage");
|
||||
expect(rowIds).toContain("comparison-row-tagline");
|
||||
});
|
||||
|
||||
it("omni variant renders 5 comparison rows", async () => {
|
||||
const { SkillsConceptCard } = await import(
|
||||
"../../src/shared/components/SkillsConceptCard.tsx"
|
||||
);
|
||||
const container = makeContainer();
|
||||
const root = createRoot(container);
|
||||
await act(async () => {
|
||||
root.render(<SkillsConceptCard variant="omni" />);
|
||||
});
|
||||
|
||||
const rows = container.querySelectorAll("[data-testid^='comparison-row-']");
|
||||
expect(rows.length).toBe(5);
|
||||
});
|
||||
|
||||
it("renders data-testid for variant", async () => {
|
||||
const { SkillsConceptCard } = await import(
|
||||
"../../src/shared/components/SkillsConceptCard.tsx"
|
||||
);
|
||||
const container = makeContainer();
|
||||
const root = createRoot(container);
|
||||
await act(async () => {
|
||||
root.render(<SkillsConceptCard variant="agent" />);
|
||||
});
|
||||
|
||||
const card = container.querySelector("[data-testid='skills-concept-card-agent']");
|
||||
expect(card).not.toBeNull();
|
||||
});
|
||||
|
||||
it("agent variant cross-link points to /dashboard/omni-skills", async () => {
|
||||
const { SkillsConceptCard } = await import(
|
||||
"../../src/shared/components/SkillsConceptCard.tsx"
|
||||
|
||||
Reference in New Issue
Block a user