All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m7s
- US1: Replace exposed provider names (DeepSeek V3 → IA Express, MiniMax → IA Avancée) in legacy_routes.py, remove 52 DeepSeek references from i18n pricing/landing keys, add generic modelName key across all 13 languages - US2: Fix glossary selector filtering (fallback to all glossaries when source lang filter returns empty), show templates by default, fix 3 broken presets (hvac/construction/automotive → hr/scientific/ecommerce) - US3: Replace 15 hardcoded French strings with i18n t() calls, increase font sizes from 8-9px to 11px, improve text contrast (opacity /20→/40, /25→/45, /30→/50) - US4: Add missing provider labels in admin ProviderStatus (deepseek, minimax, zai, google_cloud, openrouter, openrouter_premium) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import type { AdminDashboardData, ProviderStatus } from "./types";
|
|
|
|
interface ProviderStatusProps {
|
|
data: AdminDashboardData | null;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
const PROVIDER_LABELS: Record<string, string> = {
|
|
google: "Google Translate",
|
|
google_cloud: "Google Cloud",
|
|
deepl: "DeepL",
|
|
ollama: "Ollama (Local)",
|
|
openai: "OpenAI",
|
|
openrouter: "IA Essentielle",
|
|
openrouter_premium: "IA Premium",
|
|
deepseek: "IA Express",
|
|
minimax: "IA Avancée",
|
|
zai: "Grok (xAI)",
|
|
};
|
|
|
|
const STATUS_CONFIG = {
|
|
online: {
|
|
dotClass: "bg-green-500",
|
|
label: "Online",
|
|
badgeClass:
|
|
"border-green-200/30 bg-green-500/10 text-green-600",
|
|
},
|
|
degraded: {
|
|
dotClass: "bg-yellow-500",
|
|
label: "Degraded",
|
|
badgeClass:
|
|
"border-yellow-200/30 bg-yellow-500/10 text-yellow-600",
|
|
},
|
|
offline: {
|
|
dotClass: "bg-red-500",
|
|
label: "Offline",
|
|
badgeClass: "border-red-200/30 bg-red-500/10 text-red-500",
|
|
},
|
|
};
|
|
|
|
function getProviderStatus(
|
|
provider: ProviderStatus
|
|
): "online" | "degraded" | "offline" {
|
|
if (provider.available) return "online";
|
|
if (provider.error) return "offline";
|
|
return "degraded";
|
|
}
|
|
|
|
export function ProviderStatus({ data, isLoading }: ProviderStatusProps) {
|
|
const providers = Object.entries(data?.providers || {});
|
|
|
|
if (isLoading && !data) {
|
|
return (
|
|
<div className="flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3">
|
|
<div className="flex items-center justify-between">
|
|
<div className="h-3 w-32 animate-pulse rounded bg-muted" />
|
|
<div className="h-3 w-20 animate-pulse rounded bg-muted" />
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{[1, 2, 3, 4].map((i) => (
|
|
<div
|
|
key={i}
|
|
className="h-6 w-28 animate-pulse rounded bg-muted"
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (providers.length === 0) {
|
|
return (
|
|
<div className="flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Translation API Providers
|
|
</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
No provider data available
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
Translation API Providers
|
|
</span>
|
|
<span className="text-[10px] text-muted-foreground">
|
|
{providers.length} provider{providers.length !== 1 ? "s" : ""}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{providers.map(([key, provider]) => {
|
|
const status = getProviderStatus(provider);
|
|
const config = STATUS_CONFIG[status];
|
|
const label = PROVIDER_LABELS[key] || provider.name || key;
|
|
|
|
return (
|
|
<Tooltip key={key}>
|
|
<TooltipTrigger asChild>
|
|
<Badge
|
|
variant="outline"
|
|
className={`cursor-default gap-1.5 px-2.5 py-1 text-xs font-medium ${config.badgeClass}`}
|
|
>
|
|
<span
|
|
className={`size-1.5 rounded-full ${config.dotClass}`}
|
|
/>
|
|
{label}
|
|
</Badge>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<div className="flex flex-col gap-0.5 text-xs">
|
|
<span className="font-medium">{config.label}</span>
|
|
{provider.latency_ms !== undefined && (
|
|
<span className="text-muted-foreground">
|
|
Latency: {provider.latency_ms}ms
|
|
</span>
|
|
)}
|
|
{provider.last_check && (
|
|
<span className="text-muted-foreground">
|
|
Last check:{" "}
|
|
{new Date(provider.last_check).toLocaleTimeString()}
|
|
</span>
|
|
)}
|
|
{provider.error && (
|
|
<span className="text-red-500">{provider.error}</span>
|
|
)}
|
|
</div>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|