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
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
"version": "4.0.5",
"version": "4.1.0",
"type": "module",
"scripts": {
"dev": "vite",
+24
View File
@@ -51,6 +51,18 @@ export const messages = {
ai_new_project_end: 'Fin estimado',
ai_new_project_review: 'Desmarca las actividades que no quieras. Puedes cambiar el nombre aquí o después en el proyecto.',
ai_new_project_create: 'Crear proyecto',
ai_usage_title: 'Consumo por usuario',
ai_usage_month: 'Este mes',
ai_usage_all: 'Todo',
ai_usage_user: 'Usuario',
ai_usage_calls: 'Consultas',
ai_usage_input: 'Tokens entrada',
ai_usage_output: 'Tokens salida',
ai_usage_cost: 'Costo (USD)',
ai_usage_last: 'Último uso',
ai_usage_total: 'Total',
ai_usage_hint: 'costo estimado; el monto exacto está en la consola de Anthropic.',
ai_usage_empty: 'Todavía no hay consumo registrado.',
nav_projects: 'Proyectos',
nav_organization: 'Organizacion',
nav_config: 'Configuracion',
@@ -619,6 +631,18 @@ export const messages = {
ai_new_project_end: 'Estimated end',
ai_new_project_review: 'Uncheck the activities you do not want. You can change the name here or later in the project.',
ai_new_project_create: 'Create project',
ai_usage_title: 'Usage by user',
ai_usage_month: 'This month',
ai_usage_all: 'All time',
ai_usage_user: 'User',
ai_usage_calls: 'Requests',
ai_usage_input: 'Input tokens',
ai_usage_output: 'Output tokens',
ai_usage_cost: 'Cost (USD)',
ai_usage_last: 'Last used',
ai_usage_total: 'Total',
ai_usage_hint: 'estimated cost; the exact amount is in the Anthropic console.',
ai_usage_empty: 'No usage recorded yet.',
nav_projects: 'Projects',
nav_organization: 'Organization',
nav_config: 'Settings',
@@ -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">
+1
View File
@@ -6,3 +6,4 @@ export const testSmtpConfig = (data) => api.post('/settings/smtp/test', data);
export const getAiConfig = () => api.get('/settings/ai');
export const updateAiConfig = (data) => api.put('/settings/ai', data);
export const testAiConfig = (data) => api.post('/settings/ai/test', data);
export const getAiUsage = (period) => api.get(`/settings/ai/usage?period=${period}`);