fix(quotas): unifier le décompte IA (BYOK, rollback) et combler les fuites
Centralise la réserve via ai-quota, corrige admin unavailable (-1), brancher les routes sans quota et le host-pays brainstorm, avec usage-meter élargi, noms de clusters, MCP et ajustements dashboard/insights. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -294,6 +294,7 @@ export function DashboardActivityWidget({
|
||||
loading: boolean
|
||||
}) {
|
||||
const { t } = useLanguage()
|
||||
const hasActivity = data.some(d => d.count > 0)
|
||||
return (
|
||||
<DashboardWidgetShell
|
||||
widgetId="activity"
|
||||
@@ -301,11 +302,17 @@ export function DashboardActivityWidget({
|
||||
title={t('homeDashboard.widgets.activity')}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="h-24 rounded-lg bg-stone-50 animate-pulse" />
|
||||
<div className="h-24 rounded-lg bg-stone-50 dark:bg-zinc-950/40 animate-pulse" />
|
||||
) : !hasActivity ? (
|
||||
<div className="rounded-xl border border-dashed border-border/35 bg-stone-50/50 dark:bg-zinc-950/30 p-4 text-center">
|
||||
<p className="text-[10px] text-concrete leading-relaxed">{t('homeDashboard.activityEmptyHint')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<RevisionHeatmap data={data} />
|
||||
<>
|
||||
<RevisionHeatmap data={data} />
|
||||
<p className="text-[9px] text-concrete mt-2">{t('homeDashboard.widgetActivityHint')}</p>
|
||||
</>
|
||||
)}
|
||||
<p className="text-[9px] text-concrete mt-2">{t('homeDashboard.widgetActivityHint')}</p>
|
||||
</DashboardWidgetShell>
|
||||
)
|
||||
}
|
||||
@@ -329,16 +336,20 @@ export function DashboardFlashcardsProgressWidget({
|
||||
retentionRate,
|
||||
streak,
|
||||
totalCards,
|
||||
dueCount = 0,
|
||||
loading,
|
||||
onOpen,
|
||||
}: {
|
||||
retentionRate: number
|
||||
streak: number
|
||||
totalCards: number
|
||||
dueCount?: number
|
||||
loading?: boolean
|
||||
onOpen: () => void
|
||||
}) {
|
||||
const { t } = useLanguage()
|
||||
const retention = Math.max(0, Math.min(100, retentionRate))
|
||||
|
||||
return (
|
||||
<DashboardWidgetShell
|
||||
widgetId="flashcards-progress"
|
||||
@@ -347,12 +358,28 @@ export function DashboardFlashcardsProgressWidget({
|
||||
compact
|
||||
>
|
||||
{loading ? (
|
||||
<div className="h-14 rounded-lg bg-stone-50 animate-pulse" />
|
||||
<div className="space-y-2">
|
||||
<div className="h-12 rounded-lg bg-stone-50 dark:bg-zinc-950/40 animate-pulse" />
|
||||
<div className="h-8 rounded-lg bg-stone-50 dark:bg-zinc-950/40 animate-pulse" />
|
||||
</div>
|
||||
) : totalCards === 0 && dueCount === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-border/35 bg-stone-50/50 dark:bg-zinc-950/30 p-3 text-center">
|
||||
<p className="text-[10px] text-concrete leading-relaxed mb-2">
|
||||
{t('homeDashboard.flashEmptyHint')}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpen}
|
||||
className="text-[9px] font-mono font-bold uppercase text-brand-accent hover:underline"
|
||||
>
|
||||
{t('homeDashboard.flashEmptyCta')} →
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button type="button" onClick={onOpen} className="w-full text-start">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button type="button" onClick={onOpen} className="w-full text-start group">
|
||||
<div className="grid grid-cols-3 gap-2 mb-2.5">
|
||||
<div className="text-center p-2 rounded-xl border border-border/20">
|
||||
<p className="text-lg font-serif font-bold text-ink dark:text-dark-ink">{retentionRate}%</p>
|
||||
<p className="text-lg font-serif font-bold text-ink dark:text-dark-ink">{retention}%</p>
|
||||
<p className="text-[8px] font-mono uppercase text-concrete">{t('homeDashboard.flashRetention')}</p>
|
||||
</div>
|
||||
<div className="text-center p-2 rounded-xl border border-border/20">
|
||||
@@ -360,10 +387,25 @@ export function DashboardFlashcardsProgressWidget({
|
||||
<p className="text-[8px] font-mono uppercase text-concrete">{t('homeDashboard.flashStreak')}</p>
|
||||
</div>
|
||||
<div className="text-center p-2 rounded-xl border border-border/20">
|
||||
<p className="text-lg font-serif font-bold text-ink dark:text-dark-ink">{totalCards}</p>
|
||||
<p className="text-lg font-serif font-bold text-ink dark:text-dark-ink">{totalCards || dueCount}</p>
|
||||
<p className="text-[8px] font-mono uppercase text-concrete">{t('homeDashboard.flashTotal')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-stone-100 dark:bg-zinc-800 overflow-hidden mb-2">
|
||||
<div
|
||||
className="h-full rounded-full bg-brand-accent transition-all"
|
||||
style={{ width: `${retention}%` }}
|
||||
/>
|
||||
</div>
|
||||
{dueCount > 0 ? (
|
||||
<p className="text-[10px] font-medium text-brand-accent group-hover:underline">
|
||||
{t('homeDashboard.flashDueCta', { count: dueCount })} →
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[9px] text-concrete group-hover:text-brand-accent transition-colors">
|
||||
{t('homeDashboard.flashOpenCta')} →
|
||||
</p>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</DashboardWidgetShell>
|
||||
|
||||
Reference in New Issue
Block a user