feat: version 4.1.0 -- Consumo de IA por usuario en Configuracion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-23 17:48:49 -05:00
parent f497da836c
commit 120e54283e
17 changed files with 158 additions and 5 deletions
@@ -0,0 +1,47 @@
import { useState } from 'react';
import { useUi } from '../../context/ui-context.jsx';
import { useAsync } from '../../hooks/useAsync.js';
import { getAiUsage } from '../../services/settings.js';
import { ListView } from '../../components/ListView.jsx';
const fmtInt = (n) => Number(n || 0).toLocaleString();
const fmtUsd = (n) => `$${Number(n || 0).toFixed(4)}`;
// Consumo de IA por usuario (registrado en cada llamada). El costo es estimado con los precios
// por millon de tokens del modelo; el monto exacto esta en la consola de Anthropic.
export default function AiUsagePanel() {
const { t } = useUi();
const [period, setPeriod] = useState('month');
const { data } = useAsync(() => getAiUsage(period), [period]);
const columns = [
{ key: 'userName', label: t('ai_usage_user') },
{ key: 'calls', label: t('ai_usage_calls'), render: (r) => fmtInt(r.calls) },
{ key: 'inputTokens', label: t('ai_usage_input'), render: (r) => fmtInt(r.inputTokens) },
{ key: 'outputTokens', label: t('ai_usage_output'), render: (r) => fmtInt(r.outputTokens) },
{ key: 'costUsd', label: t('ai_usage_cost'), render: (r) => fmtUsd(r.costUsd) },
{ key: 'lastUsed', label: t('ai_usage_last'), render: (r) => (r.lastUsed ? new Date(r.lastUsed).toLocaleString() : '—') },
];
return (
<div className="panel-card" style={{ marginTop: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' }}>
<h4 style={{ margin: 0 }}>{t('ai_usage_title')}</h4>
<select className="input" style={{ width: 'auto' }} value={period} onChange={(e) => setPeriod(e.target.value)}>
<option value="month">{t('ai_usage_month')}</option>
<option value="all">{t('ai_usage_all')}</option>
</select>
</div>
{!data ? <div className="text-muted">{t('common_loading')}</div> : (
<>
<p className="text-muted" style={{ fontSize: '.8rem' }}>
{t('ai_usage_total')}: <strong>{fmtInt(data.total.calls)}</strong> {t('ai_usage_calls').toLowerCase()} · {fmtInt(data.total.inputTokens)} / {fmtInt(data.total.outputTokens)} tokens · <strong>{fmtUsd(data.total.costUsd)}</strong> — {t('ai_usage_hint')}
</p>
{data.users.length ? (
<ListView columns={columns} rows={data.users} getRowKey={(r) => r.userId ?? 'deleted'} />
) : <div className="text-muted">{t('ai_usage_empty')}</div>}
</>
)}
</div>
);
}
@@ -9,6 +9,7 @@ import { ComboBox } from '../../components/ComboBox.jsx';
import { CatalogEditor } from './CatalogEditor.jsx';
import SmtpSettingsPanel from './SmtpSettingsPanel.jsx';
import AiSettingsPanel from './AiSettingsPanel.jsx';
import AiUsagePanel from './AiUsagePanel.jsx';
import CompanySettingsPanel from './CompanySettingsPanel.jsx';
import UpdaterPanel from './UpdaterPanel.jsx';
import ErrorLogsPanel from './ErrorLogsPanel.jsx';
@@ -122,6 +123,7 @@ export default function ConfiguracionPage() {
<Expander icon={Sparkles} title={t('config_ai')} draftForms={['ai-config']}>
<AiSettingsPanel />
<AiUsagePanel />
</Expander>
<div id="updates-panel">