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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user