diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 4762d8810c..02b9941eca 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -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": { diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 2c4ede7e18..f471ef67dd 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -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": { diff --git a/src/shared/components/SkillsConceptCard.tsx b/src/shared/components/SkillsConceptCard.tsx index 2b5fc67535..407a6fe01a 100644 --- a/src/shared/components/SkillsConceptCard.tsx +++ b/src/shared/components/SkillsConceptCard.tsx @@ -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 (
-
- {icon} + {/* Header row */} +
+
+ {icon} +
+
+

{title}

+
+ + {crossLinkLabel} + arrow_forward +
-
-

{title}

-

{description}

+ + {/* Comparison table */} +
+ {/* Column headers */} +
+
+
+ {t("conceptCard.comparison.colAgent")} +
+
+ {t("conceptCard.comparison.colOmni")} +
+
+ + {/* Rows */} + {COMPARISON_ROWS.map((row: ComparisonRow, i) => ( +
+
+ {t(`conceptCard.comparison.${row}.label`)} +
+
+ {t(`conceptCard.comparison.${row}.agent`)} +
+
+ {t(`conceptCard.comparison.${row}.omni`)} +
+
+ ))}
- - {crossLinkLabel} - arrow_forward -
); } diff --git a/tests/unit/SkillsConceptCard.test.tsx b/tests/unit/SkillsConceptCard.test.tsx index 010478126a..4ee79a77e4 100644 --- a/tests/unit/SkillsConceptCard.test.tsx +++ b/tests/unit/SkillsConceptCard.test.tsx @@ -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(); + }); + + 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(); + }); + + 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(); + }); + + 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"