'use client'; import { useState, useEffect } from 'react'; import { Loader2, TrendingUp, Zap, Image as Img, RefreshCw } from 'lucide-react'; const PERIODS = [ { v: 'today', label: 'Сегодня' }, { v: 'week', label: '7 дней' }, { v: 'month', label: '30 дней' }, { v: 'alltime', label: 'Всё время' }, ]; const PROVIDER_LABELS = { 'aiprimetech': 'aiprimetech.io', 'routerai': 'routerai.ru', 'nyxos': 'Nyxos Plus', 'aiguoguo': 'aiguoguo', }; const TYPE_LABELS = { 'chat': '💬 Текст', 'image': '🖼 Изображение', 'image_via_responses': '🖼 Изображение (responses)', 'article': '📝 Статья', 'topic': '🔍 Топики', }; function fmt(n) { return Number(n || 0).toFixed(2); } function fmtInt(n) { return Number(n || 0).toLocaleString('ru-RU'); } export default function SpendingPage() { const [period, setPeriod] = useState('month'); const [data, setData] = useState(null); const [byProvider, setByProvider] = useState(null); const [loading, setLoading] = useState(true); async function load(p) { setLoading(true); try { const [r1, r2] = await Promise.all([ fetch(`/api/usage/summary?range=${p}&group_by=request_type`).then(r => r.json()), fetch(`/api/usage/summary?range=${p}&group_by=provider`).then(r => r.json()), ]); setData(r1); setByProvider(r2); } catch {} setLoading(false); } useEffect(() => { load(period); }, [period]); const totals = data?.totals || {}; // Расходы по ключевым провайдерам const aiprimetech = byProvider?.breakdown?.find(b => b.key === 'aiprimetech'); const routerai = byProvider?.breakdown?.find(b => b.key === 'routerai'); const nyxos = byProvider?.breakdown?.find(b => b.key === 'nyxos' || b.key?.includes('nyxos')); return (

Расходы на AI

Только aiprimetech.io и routerai.ru

{PERIODS.map(p => ( ))}
{loading &&
} {!loading && data && ( <> {/* Итого */}
{[ { label: 'Итого, ₽', value: `₽ ${fmt(totals.cost_rub)}`, accent: true }, { label: 'Запросов', value: fmtInt(totals.calls) }, { label: 'Токенов', value: fmtInt((totals.prompt_tokens||0) + (totals.completion_tokens||0)) }, { label: 'Картинок', value: fmtInt(totals.image_count) }, ].map(s => (
{s.value}
{s.label}
))}
{/* По провайдерам */}

По провайдерам

{[ { key: 'aiprimetech', label: 'aiprimetech.io', icon: '💬', desc: 'Текстовая генерация', data: aiprimetech }, { key: 'routerai', label: 'routerai.ru', icon: '🖼', desc: 'Генерация изображений', data: routerai }, ].map(p => { const d = p.data; return (
{p.icon}
{p.label}
{p.desc}
₽ {fmt(d?.cost_rub)}
{fmtInt(d?.calls)}
запросов
{d?.failed||0}
ошибок
{fmtInt(d?.image_count||((d?.prompt_tokens||0)+(d?.completion_tokens||0)))}
{p.key==='routerai'?'картинок':'токенов'}
); })}
{/* Разбивка по типу запроса */}

По типу операции

{(data.breakdown || []).map((row, i) => ( ))} {(!data.breakdown?.length) && ( )}
Операция Запросов Ошибок Токены / Картинки Стоимость, ₽
{TYPE_LABELS[row.key] || row.key} {fmtInt(row.calls)} {row.failed || 0} {row.image_count > 0 ? `${row.image_count} шт.` : fmtInt((row.prompt_tokens||0)+(row.completion_tokens||0))} ₽ {fmt(row.cost_rub)}
Нет данных за период
Итого {fmtInt(totals.calls)} {totals.failed||0} ₽ {fmt(totals.cost_rub)}
)}
); }