mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
Integrated into release/v3.8.8. Applied review fixes: moved the SELECT 1 into a pingDb() db helper (no raw SQL in route, Hard Rule #5) + the 503 catch no longer leaks err.message (Hard Rule #12). Thanks @herjarsa!
This commit is contained in:
committed by
GitHub
parent
009ad13a91
commit
fd26e601a2
82
src/shared/components/cli/CliComparisonCard.tsx
Normal file
82
src/shared/components/cli/CliComparisonCard.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
import type { CliConceptType } from "./CliConceptCard";
|
||||
|
||||
export interface CliComparisonCardProps {
|
||||
currentType: CliConceptType;
|
||||
}
|
||||
|
||||
const TYPE_HREFS: Record<CliConceptType, string> = {
|
||||
code: "/dashboard/cli-code",
|
||||
agent: "/dashboard/cli-agents",
|
||||
acp: "/dashboard/acp-agents",
|
||||
};
|
||||
|
||||
export default function CliComparisonCard({ currentType }: CliComparisonCardProps) {
|
||||
const t = useTranslations("cliCommon");
|
||||
|
||||
const types: CliConceptType[] = ["code", "agent", "acp"];
|
||||
|
||||
return (
|
||||
<div className="bg-surface border border-black/5 dark:border-white/5 rounded-lg shadow-sm p-4">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{types.map((type) => {
|
||||
const isCurrent = type === currentType;
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
className={cn(
|
||||
"flex flex-col gap-2 p-3 rounded-lg",
|
||||
isCurrent
|
||||
? "bg-primary/10 border border-primary/30"
|
||||
: "bg-black/[0.02] dark:bg-white/[0.02] border border-transparent"
|
||||
)}
|
||||
>
|
||||
{/* Title */}
|
||||
<div className="flex items-center justify-between gap-1 flex-wrap">
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs font-semibold uppercase tracking-wider",
|
||||
isCurrent ? "text-primary" : "text-text-muted"
|
||||
)}
|
||||
>
|
||||
{t(`comparison.${type}.title`)}
|
||||
</span>
|
||||
{isCurrent ? (
|
||||
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-primary/20 text-primary whitespace-nowrap">
|
||||
{t("comparison.thisPage")} ✓
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={TYPE_HREFS[type]}
|
||||
className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted hover:text-primary hover:bg-primary/10 transition-colors whitespace-nowrap"
|
||||
>
|
||||
Ver →
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-[11px] text-text-muted leading-relaxed">
|
||||
{t(`comparison.${type}.desc`)}
|
||||
</p>
|
||||
|
||||
{/* Flow */}
|
||||
<p className="text-[10px] text-text-muted font-mono">
|
||||
{t(`comparison.${type}.flow`)}
|
||||
</p>
|
||||
|
||||
{/* Examples */}
|
||||
<p className="text-[10px] text-text-muted italic">
|
||||
{t(`comparison.${type}.examples`)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
src/shared/components/cli/CliConceptCard.tsx
Normal file
58
src/shared/components/cli/CliConceptCard.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
|
||||
export type CliConceptType = "code" | "agent" | "acp";
|
||||
|
||||
export interface CliConceptCardProps {
|
||||
currentType: CliConceptType;
|
||||
}
|
||||
|
||||
const TYPE_HREFS: Record<CliConceptType, string> = {
|
||||
code: "/dashboard/cli-code",
|
||||
agent: "/dashboard/cli-agents",
|
||||
acp: "/dashboard/acp-agents",
|
||||
};
|
||||
|
||||
export default function CliConceptCard({ currentType }: CliConceptCardProps) {
|
||||
const t = useTranslations("cliCommon");
|
||||
|
||||
const types: CliConceptType[] = ["code", "agent", "acp"];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-surface border rounded-lg shadow-sm p-4",
|
||||
"border-primary/30 bg-primary/5"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Current type — highlighted */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-primary">
|
||||
{t(`concept.${currentType}.title`)}
|
||||
</span>
|
||||
<p className="text-sm text-text-muted">{t(`concept.${currentType}.phrase`)}</p>
|
||||
<p className="text-[11px] text-text-muted font-mono">{t(`concept.${currentType}.flow`)}</p>
|
||||
</div>
|
||||
|
||||
{/* Other types as chips */}
|
||||
<div className="flex items-center gap-2 flex-wrap pt-1 border-t border-black/5 dark:border-white/5">
|
||||
{types
|
||||
.filter((type) => type !== currentType)
|
||||
.map((type) => (
|
||||
<Link
|
||||
key={type}
|
||||
href={TYPE_HREFS[type]}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted hover:text-primary hover:bg-primary/10 transition-colors"
|
||||
>
|
||||
{t(`concept.${type}.title`)} — {t(`concept.${type}.seeOther`)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
152
src/shared/components/cli/CliToolCard.tsx
Normal file
152
src/shared/components/cli/CliToolCard.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog";
|
||||
import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus";
|
||||
import CliStatusBadge from "@/app/(dashboard)/dashboard/cli-code/components/CliStatusBadge";
|
||||
import { cn } from "@/shared/utils/cn";
|
||||
|
||||
export interface CliToolCardProps {
|
||||
tool: CliCatalogEntry;
|
||||
batchStatus: ToolBatchStatus | null;
|
||||
detailHref: string;
|
||||
hasActiveProviders: boolean;
|
||||
}
|
||||
|
||||
export default function CliToolCard({
|
||||
tool,
|
||||
batchStatus,
|
||||
detailHref,
|
||||
hasActiveProviders,
|
||||
}: CliToolCardProps) {
|
||||
const installed = batchStatus?.detection.installed ?? false;
|
||||
const configStatus = batchStatus?.config.status ?? null;
|
||||
const version = batchStatus?.detection.version ?? "not found";
|
||||
const endpoint = batchStatus?.config.endpoint ?? null;
|
||||
|
||||
const showInstallChips = !installed && tool.configType !== "guide";
|
||||
|
||||
const title = (
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* Icon / image */}
|
||||
{tool.image ? (
|
||||
<Image
|
||||
src={tool.image}
|
||||
alt={tool.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-md object-contain flex-shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="material-symbols-outlined text-[20px] flex-shrink-0"
|
||||
style={{ color: tool.color }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{tool.icon ?? "terminal"}
|
||||
</span>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-semibold text-text-main text-sm leading-tight truncate">
|
||||
{tool.name}
|
||||
</span>
|
||||
<span className="text-[11px] text-text-muted font-mono bg-black/5 dark:bg-white/5 px-1.5 py-0.5 rounded">
|
||||
{version}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted line-clamp-1 mt-0.5">{tool.description}</p>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-[18px] text-text-muted flex-shrink-0">
|
||||
chevron_right
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={detailHref}
|
||||
className={cn(
|
||||
"block min-h-[180px]",
|
||||
"bg-surface border border-black/5 dark:border-white/5 rounded-lg shadow-sm",
|
||||
"hover:shadow-md hover:border-primary/30 transition-all",
|
||||
"p-4 flex flex-col gap-3"
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
{title}
|
||||
|
||||
{/* Status strip */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Detection */}
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 text-[11px] font-medium px-1.5 py-0.5 rounded",
|
||||
installed
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-zinc-500 dark:text-zinc-400"
|
||||
)}
|
||||
>
|
||||
<span aria-hidden="true">{installed ? "✓" : "✗"}</span>
|
||||
{installed ? "Detectado" : "Não detectado"}
|
||||
</span>
|
||||
|
||||
{/* Config status */}
|
||||
{configStatus && (
|
||||
<CliStatusBadge
|
||||
effectiveConfigStatus={configStatus}
|
||||
batchStatus={null}
|
||||
lastConfiguredAt={batchStatus?.config.lastConfiguredAt ?? null}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Endpoint */}
|
||||
{endpoint && (
|
||||
<span className="text-[10px] text-text-muted font-mono truncate max-w-[140px]">
|
||||
{endpoint}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Badges row */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{tool.baseUrlSupport === "partial" && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400">
|
||||
<span aria-hidden="true">⚠</span> Base URL parcial
|
||||
</span>
|
||||
)}
|
||||
{tool.acpSpawnable === true && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-blue-500/10 text-blue-600 dark:text-blue-400">
|
||||
também ACP
|
||||
</span>
|
||||
)}
|
||||
{showInstallChips && (
|
||||
<>
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted">
|
||||
📋 Manual config
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[11px] font-medium rounded-full bg-black/5 dark:bg-white/5 text-text-muted">
|
||||
⬇ Install
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-auto pt-1 flex items-center justify-between">
|
||||
<span className="text-xs text-primary font-medium">
|
||||
{installed ? "Configurar →" : "Como instalar →"}
|
||||
</span>
|
||||
{!hasActiveProviders && (
|
||||
<span
|
||||
className="text-[10px] text-text-muted italic"
|
||||
title="Conecte um provider em Providers"
|
||||
>
|
||||
Conecte um provider em Providers
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
8
src/shared/components/cli/index.ts
Normal file
8
src/shared/components/cli/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { default as CliToolCard } from "./CliToolCard";
|
||||
export type { CliToolCardProps } from "./CliToolCard";
|
||||
|
||||
export { default as CliConceptCard } from "./CliConceptCard";
|
||||
export type { CliConceptCardProps, CliConceptType } from "./CliConceptCard";
|
||||
|
||||
export { default as CliComparisonCard } from "./CliComparisonCard";
|
||||
export type { CliComparisonCardProps } from "./CliComparisonCard";
|
||||
Reference in New Issue
Block a user