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:
@@ -29,6 +29,7 @@ import {
|
||||
} from '@/components/dashboard-path-widgets'
|
||||
import type { DashboardPath } from '@/lib/dashboard/path-types'
|
||||
import { buildFastPathsFromBriefing } from '@/lib/dashboard/paths-fast'
|
||||
import { emitAiUsageChanged } from '@/lib/ai-usage-sync'
|
||||
import {
|
||||
DashboardInboxWidget,
|
||||
DashboardRevisionWidget,
|
||||
@@ -192,7 +193,8 @@ function localeForLanguage(language: string): string {
|
||||
return map[language] || language
|
||||
}
|
||||
|
||||
function stripHtml(html: string): string {
|
||||
function stripHtml(html: string | null | undefined): string {
|
||||
if (!html) return ''
|
||||
return html.replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
@@ -293,7 +295,10 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
const loadSentiment = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/briefing/sentiment', { cache: 'no-store' })
|
||||
if (res.ok) setSentiment(await res.json())
|
||||
if (res.ok) {
|
||||
setSentiment(await res.json())
|
||||
emitAiUsageChanged()
|
||||
}
|
||||
} catch {
|
||||
/* widget sentiment en état vide */
|
||||
} finally {
|
||||
@@ -322,15 +327,15 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
fetch('/api/flashcards/stats', { cache: 'no-store' })
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(json => {
|
||||
if (json) {
|
||||
setFlashcardStats({
|
||||
retentionRate: json.retentionRate ?? 0,
|
||||
streak: json.streak ?? 0,
|
||||
totalCards: json.totalCards ?? 0,
|
||||
})
|
||||
}
|
||||
setFlashcardStats({
|
||||
retentionRate: json?.retentionRate ?? 0,
|
||||
streak: json?.streak ?? 0,
|
||||
totalCards: json?.totalCards ?? 0,
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
setFlashcardStats({ retentionRate: 0, streak: 0, totalCards: 0 })
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [loadBriefing, loadMindMap])
|
||||
|
||||
// Pistes rapides dès le briefing, puis enrichissement en arrière-plan
|
||||
@@ -482,6 +487,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
if (!res.ok) throw new Error(json.error)
|
||||
if (json.insight) {
|
||||
await Promise.all([loadBriefing(), loadPaths()])
|
||||
emitAiUsageChanged()
|
||||
toast.success(t('homeDashboard.echoFound'))
|
||||
} else {
|
||||
toast.info(t('homeDashboard.echoNone'))
|
||||
@@ -789,6 +795,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
retentionRate={flashcardStats?.retentionRate ?? 0}
|
||||
streak={flashcardStats?.streak ?? 0}
|
||||
totalCards={flashcardStats?.totalCards ?? 0}
|
||||
dueCount={dueFlashcards}
|
||||
loading={!flashcardStats}
|
||||
onOpen={() => router.push('/revision')}
|
||||
/>,
|
||||
@@ -799,6 +806,14 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
notes={resumeNotes}
|
||||
loading={briefingLoading}
|
||||
onSelect={onNoteSelect}
|
||||
onCaptureFocus={() => {
|
||||
document.getElementById('dashboard-widget-capture')?.scrollIntoView({
|
||||
behavior: prefersReducedMotion ? 'auto' : 'smooth',
|
||||
block: 'center',
|
||||
})
|
||||
const ta = document.querySelector<HTMLTextAreaElement>('#dashboard-widget-capture textarea')
|
||||
ta?.focus()
|
||||
}}
|
||||
formatRelativeTime={relTime}
|
||||
prefersReducedMotion={!!prefersReducedMotion}
|
||||
/>,
|
||||
@@ -1005,7 +1020,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
|
||||
<div className="h-full w-full bg-[#F9F8F6] dark:bg-[#0D0D0D] overflow-y-auto custom-scrollbar relative">
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808005_1px,transparent_1px),linear-gradient(to_bottom,#80808005_1px,transparent_1px)] bg-[size:28px_28px] pointer-events-none z-0" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-10 py-6 sm:py-8 relative z-10">
|
||||
<div className="w-full px-4 sm:px-6 lg:px-8 xl:px-10 2xl:px-12 py-6 sm:py-8 relative z-10">
|
||||
{/* ── En-tête : orientation en 2 secondes ── */}
|
||||
<header className="mb-5">
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-2 pb-4 border-b border-border/20">
|
||||
|
||||
Reference in New Issue
Block a user