All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m57s
Pricing: 'deepl' removed from plan provider lists and the comparison table row. Landing and pricing copy (13 locales) no longer mention DeepL (65 values cleaned, 39 dead keys deleted: pricing.comparison.deepl, providerTheme.classic.deepl.*). Providers: available-provider responses are filtered so a DeepL entry from the backend can never surface in the engine picker or the services page; DeepL theme entry and static lib/api.ts list entry removed. Admin: DeepL config card, type fields, defaults and fallback-chain mentions removed; stats/chart/status maps and mock data cleaned. Backend adapter untouched — DeepL is invisible app-wide via the UI filter; removing the Python adapter itself is a separate step if wanted. Verified: build exit 0, vitest 9/9, eslint clean on touched files, zero 'deepl' occurrences outside the two intentional UI filters.
114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { Cpu } from "lucide-react";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import type { TranslationStatsData } from "./types";
|
|
|
|
interface ProviderBreakdownChartProps {
|
|
data: TranslationStatsData | null;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
const providerLabels: Record<string, string> = {
|
|
google: "Google Translate",
|
|
ollama: "Ollama (Local)",
|
|
openai: "OpenAI",
|
|
};
|
|
|
|
const providerColors: Record<string, string> = {
|
|
google: "bg-blue-500",
|
|
ollama: "bg-green-500",
|
|
openai: "bg-purple-500",
|
|
};
|
|
|
|
export function ProviderBreakdownChart({ data, isLoading }: ProviderBreakdownChartProps) {
|
|
if (isLoading) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Cpu className="h-5 w-5" />
|
|
Répartition par Provider
|
|
</CardTitle>
|
|
<CardDescription>Chargement...</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{[1, 2, 3, 4].map((i) => (
|
|
<div key={i} className="space-y-2">
|
|
<div className="h-4 w-20 animate-pulse rounded bg-muted" />
|
|
<div className="h-2 w-full animate-pulse rounded bg-muted" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!data?.provider_breakdown) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Cpu className="h-5 w-5" />
|
|
Répartition par Provider
|
|
</CardTitle>
|
|
<CardDescription>Aucune donnée disponible</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center py-8 text-muted-foreground">
|
|
<p className="text-sm">Aucune donnée de provider</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const providers = Object.entries(data.provider_breakdown).filter(
|
|
([, value]) => value.count > 0
|
|
);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Cpu className="h-5 w-5" />
|
|
Répartition par Provider
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Distribution des traductions par fournisseur de service
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{providers.map(([provider, value]) => (
|
|
<div key={provider} className="space-y-2">
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="font-medium">
|
|
{providerLabels[provider] || provider}
|
|
</span>
|
|
<span className="text-muted-foreground">
|
|
{value.count} ({value.percentage.toFixed(1)}%)
|
|
</span>
|
|
</div>
|
|
<div className="relative h-2 w-full overflow-hidden rounded-full bg-secondary">
|
|
<div
|
|
className={`h-full ${providerColors[provider] || "bg-primary"} transition-all duration-500`}
|
|
style={{ width: `${value.percentage}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|