feat: refactor admin panel interactions to use Discord.js 14.22.1 API with enhanced ownership checks and updated UI components

This commit is contained in:
2025-10-02 22:50:27 -05:00
parent 66c46ef6df
commit 0e8aae77b1
5 changed files with 243 additions and 479 deletions

View File

@@ -1,67 +1,59 @@
import logger from "../../core/lib/logger";
import { ButtonInteraction, MessageFlags, PermissionFlagsBits } from 'discord.js';
import { ButtonInteraction, MessageFlags, ContainerBuilder, TextDisplayBuilder, SectionBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import { aiService } from '../../core/services/AIService';
const OWNER_ID = '327207082203938818'; // Solo el dueño puede usar este panel
export default {
customId: 'ai_clear_cache',
run: async (interaction: ButtonInteraction) => {
// Verificar permisos de administrador
if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
return interaction.reply({
content: '❌ No tienes permisos de administrador para usar este botón.',
flags: MessageFlags.Ephemeral
// Verificar que sea el dueño del bot (CRÍTICO)
if (interaction.user.id !== OWNER_ID) {
return interaction.reply({
content: '❌ Solo el dueño del bot puede usar este panel administrativo.',
flags: MessageFlags.Ephemeral
});
}
try {
await interaction.deferUpdate();
// Limpiar cache de conversaciones (simular limpieza)
// Limpiar cache de conversaciones
const stats = aiService.getStats();
const conversationsCleared = stats.activeConversations;
// Aquí podrías agregar lógica real para limpiar el cache
// Por ejemplo: aiService.clearConversations();
// @ts-ignore
const successPanel = {
type: 17,
accent_color: 0x00FF00,
components: [
{
type: 10,
content: '## 🧹 Cache Limpiado Exitosamente'
},
{
type: 10,
content: `-# Se han limpiado ${conversationsCleared} conversaciones activas.`
},
{ type: 14, divider: true, spacing: 1 },
{
type: 10,
content: `✅ **Estado:** Cache limpiado\n🔄 **Conversaciones eliminadas:** ${conversationsCleared}\n⏰ **Timestamp:** ${new Date().toISOString().replace('T', ' ').split('.')[0]} UTC\n👤 **Administrador:** ${interaction.user.username}`
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: "🔙 Volver al panel principal de administración" }
],
accessory: {
type: 2,
style: 2,
emoji: "🔙",
label: 'Volver al Panel',
custom_id: 'ai_refresh_stats'
}
}
]
};
// Aquí iría la lógica real de limpieza:
// aiService.clearAllConversations();
// Crear container de éxito usando la API real
const successContainer = new ContainerBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent('## 🧹 Cache Limpiado Exitosamente\n-# Se han limpiado ' + conversationsCleared + ' conversaciones activas.\n\n✅ **Estado:** Cache limpiado\n🔄 **Conversaciones eliminadas:** ' + conversationsCleared + '\n⏰ **Timestamp:** ' + new Date().toISOString().replace('T', ' ').split('.')[0] + ' UTC\n👤 **Dueño:** ' + interaction.user.username)
)
.addSectionComponents(
new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent("🔙 Volver al panel principal de administración")
)
.setButtonAccessory(
new ButtonBuilder()
.setCustomId('ai_refresh_stats')
.setLabel('Volver al Panel')
.setEmoji('🔙')
.setStyle(ButtonStyle.Primary)
)
);
await interaction.message.edit({
components: [successContainer],
flags: MessageFlags.IsComponentsV2
});
logger.info(`Cache de IA limpiado por el dueño ${interaction.user.username} (${interaction.user.id})`);
await interaction.message.edit({ components: [successPanel] });
logger.info(`Cache de IA limpiado por ${interaction.user.username} (${interaction.user.id})`);
} catch (error) {
//@ts-ignore
logger.error('Error limpiando cache de IA:', error);
if (!interaction.deferred && !interaction.replied) {
await interaction.reply({

View File

@@ -1,38 +1,30 @@
import logger from "../../core/lib/logger";
import { ButtonInteraction, MessageFlags, PermissionFlagsBits } from 'discord.js';
import { ButtonInteraction, MessageFlags, ContainerBuilder, TextDisplayBuilder, SectionBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
const OWNER_ID = '327207082203938818'; // Solo el dueño puede usar este panel
export default {
customId: 'ai_config',
run: async (interaction: ButtonInteraction) => {
// Verificar permisos de administrador
if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
return interaction.reply({
content: '❌ No tienes permisos de administrador para usar este botón.',
flags: MessageFlags.Ephemeral
// Verificar que sea el dueño del bot (CRÍTICO)
if (interaction.user.id !== OWNER_ID) {
return interaction.reply({
content: '❌ Solo el dueño del bot puede usar este panel administrativo.',
flags: MessageFlags.Ephemeral
});
}
try {
await interaction.deferUpdate();
// Panel de configuración detallada
// @ts-ignore
const configPanel = {
type: 17,
accent_color: 0x3498DB,
components: [
{
type: 10,
content: '## ⚙️ Configuración del Sistema de IA'
},
{
type: 10,
content: '-# Ajustes avanzados y configuración del servicio Gemini-chan.'
},
{ type: 14, divider: true, spacing: 1 },
{
type: 10,
content: `## 🔧 Configuración Actual
// Panel de configuración usando la API real de Discord.js 14.22.1
const configContainer = new ContainerBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent(`## ⚙️ Configuración del Sistema de IA
-# Ajustes avanzados y configuración del servicio Gemini-chan.
## 🔧 Configuración Actual
\`\`\`yaml
# Límites de Rate Limiting
rate_limit_max: 20 # requests por minuto por usuario
@@ -59,71 +51,31 @@ model: "gemini-1.5-flash" # modelo de Google AI
temperature: 0.7 # creatividad de respuestas
top_p: 0.85 # diversidad de tokens
top_k: 40 # límite de candidatos
\`\`\`
\`\`\``)
)
.addSectionComponents(
new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent("🔙 Volver al panel principal de administración")
)
.setButtonAccessory(
new ButtonBuilder()
.setCustomId('ai_refresh_stats')
.setLabel('Volver al Panel')
.setEmoji('🔙')
.setStyle(ButtonStyle.Primary)
)
);
## 🔄 Opciones Disponibles`
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: "📊 **Ver logs del sistema** en tiempo real" }
],
accessory: {
type: 2,
style: 2,
emoji: "📊",
label: 'Ver Logs',
custom_id: 'ai_view_logs'
}
},
{
type: 9,
components: [
{ type: 10, content: "🔧 **Cambiar configuración** de rate limits y timeouts" }
],
accessory: {
type: 2,
style: 2,
emoji: "🔧",
label: 'Configurar',
custom_id: 'ai_modify_config'
}
},
{
type: 9,
components: [
{ type: 10, content: "🧪 **Modo de prueba** para testing del sistema" }
],
accessory: {
type: 2,
style: 2,
emoji: "🧪",
label: 'Modo Test',
custom_id: 'ai_test_mode'
}
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: "🔙 Volver al panel principal de administración" }
],
accessory: {
type: 2,
style: 1,
emoji: "🔙",
label: 'Volver al Panel',
custom_id: 'ai_refresh_stats'
}
}
]
};
await interaction.message.edit({
components: [configContainer],
flags: MessageFlags.IsComponentsV2
});
logger.info(`Panel de configuración de IA accedido por el dueño ${interaction.user.username} (${interaction.user.id})`);
await interaction.message.edit({ components: [configPanel] });
logger.info(`Panel de configuración de IA accedido por ${interaction.user.username} (${interaction.user.id})`);
} catch (error) {
//@ts-ignore
logger.error('Error mostrando configuración de IA:', error);
if (!interaction.deferred && !interaction.replied) {
await interaction.reply({

View File

@@ -1,5 +1,5 @@
import logger from "../../core/lib/logger";
import { ButtonInteraction, MessageFlags, PermissionFlagsBits } from 'discord.js';
import { ButtonInteraction, MessageFlags, ContainerBuilder, TextDisplayBuilder, SectionBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import { aiService } from '../../core/services/AIService';
const OWNER_ID = '327207082203938818'; // Solo el dueño puede hacer reset completo
@@ -7,7 +7,7 @@ const OWNER_ID = '327207082203938818'; // Solo el dueño puede hacer reset compl
export default {
customId: 'ai_full_reset',
run: async (interaction: ButtonInteraction) => {
// Verificar que sea el dueño del bot (reset completo es crítico)
// Verificar que sea el dueño del bot (reset completo es CRÍTICO)
if (interaction.user.id !== OWNER_ID) {
return interaction.reply({
content: '❌ Solo el dueño del bot puede realizar un reset completo del sistema de IA.',
@@ -23,33 +23,22 @@ export default {
const conversationsCleared = statsBefore.activeConversations;
const requestsCleared = statsBefore.queueLength;
// Aquí irían las funciones reales de reset del servicio
// Por ejemplo:
// Aquí irían las funciones reales de reset del servicio:
// aiService.fullReset();
// aiService.clearAllConversations();
// aiService.clearRequestQueue();
// aiService.resetStatistics();
const resetTimestamp = new Date().toISOString().replace('T', ' ').split('.')[0];
// Panel de confirmación de reset completo
// @ts-ignore
const resetCompletePanel = {
type: 17,
accent_color: 0xFF4444,
components: [
{
type: 10,
content: '## ⚠️ RESET COMPLETO EJECUTADO'
},
{
type: 10,
content: '-# El sistema de IA ha sido completamente reiniciado.'
},
{ type: 14, divider: true, spacing: 1 },
{
type: 10,
content: `## 🔄 Resumen del Reset
// Panel de confirmación de reset completo usando la API real
const resetCompleteContainer = new ContainerBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent(`## ⚠️ RESET COMPLETO EJECUTADO
-# El sistema de IA ha sido completamente reiniciado.
## 🔄 Resumen del Reset
\`\`\`
┌─────────────────────────┬─────────────┐
│ Elemento Limpiado │ Cantidad │
@@ -69,12 +58,9 @@ export default {
⚠️ ADVERTENCIA: Todas las conversaciones activas
han sido eliminadas permanentemente.
\`\`\``
},
{ type: 14, divider: true, spacing: 1 },
{
type: 10,
content: `## ✅ Sistema Restaurado
\`\`\`
## ✅ Sistema Restaurado
El sistema de IA ha vuelto a su estado inicial:
• **Memoria limpia** - Sin conversaciones previas
@@ -83,45 +69,46 @@ El sistema de IA ha vuelto a su estado inicial:
• **Configuración default** - Valores originales
• **Cache limpio** - Memoria optimizada
El sistema está listo para recibir nuevas consultas.`
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: "🔙 Volver al panel principal (con datos reset)" }
],
accessory: {
type: 2,
style: 1,
emoji: "🔙",
label: 'Volver al Panel',
custom_id: 'ai_refresh_stats'
}
},
{
type: 9,
components: [
{ type: 10, content: "⚠️ **REALIZAR OTRO RESET** (solo si es necesario)" }
],
accessory: {
type: 2,
style: 4,
emoji: "⚠️",
label: 'Reset Nuevamente',
custom_id: 'ai_full_reset'
}
}
]
};
El sistema está listo para recibir nuevas consultas.`)
)
.addSectionComponents(
new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent("🔙 Volver al panel principal (con datos reset)")
)
.setButtonAccessory(
new ButtonBuilder()
.setCustomId('ai_refresh_stats')
.setLabel('Volver al Panel')
.setEmoji('🔙')
.setStyle(ButtonStyle.Primary)
),
new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder()
.setContent("⚠️ **REALIZAR OTRO RESET** (solo si es necesario)")
)
.setButtonAccessory(
new ButtonBuilder()
.setCustomId('ai_full_reset')
.setLabel('Reset Nuevamente')
.setEmoji('⚠️')
.setStyle(ButtonStyle.Danger)
)
);
await interaction.message.edit({
components: [resetCompleteContainer],
flags: MessageFlags.IsComponentsV2
});
await interaction.message.edit({ components: [resetCompletePanel] });
// Log crítico del reset completo
logger.warn(`🚨 RESET COMPLETO DE IA ejecutado por ${interaction.user.username} (${interaction.user.id})`);
logger.warn(`🚨 RESET COMPLETO DE IA ejecutado por el dueño ${interaction.user.username} (${interaction.user.id})`);
logger.info(`Reset stats - Conversaciones: ${conversationsCleared}, Queue: ${requestsCleared}, Timestamp: ${resetTimestamp}`);
} catch (error) {
//@ts-ignore
logger.error('Error ejecutando reset completo de IA:', error);
if (!interaction.deferred && !interaction.replied) {
await interaction.reply({

View File

@@ -1,164 +1,33 @@
import logger from "../../core/lib/logger";
import { ButtonInteraction, MessageFlags, PermissionFlagsBits } from 'discord.js';
import { ButtonInteraction, MessageFlags, ContainerBuilder, TextDisplayBuilder, SectionBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import { buildAIAdminPanel } from '../../commands/messages/AI/stats';
/**
* Formatear tiempo de actividad
*/
function formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
}
/**
* Formatear bytes a formato legible
*/
function formatBytesMB(bytes: number): string {
return (bytes / 1024 / 1024).toFixed(1) + 'MB';
}
/**
* Construir panel de administración de IA actualizado
*/
function buildRefreshedAIPanel() {
const { aiService } = require('../../core/services/AIService');
const stats = aiService.getStats();
const uptime = process.uptime();
const memoryUsage = process.memoryUsage();
const now = new Date();
const ts = now.toISOString().replace('T', ' ').split('.')[0];
// Estados del sistema
const queueStatus = stats.queueLength === 0 ? '🟢 Normal' :
stats.queueLength < 5 ? '🟡 Ocupado' : '🔴 Saturado';
const memoryStatus = memoryUsage.heapUsed / memoryUsage.heapTotal > 0.8 ? '🔴 Alta' : '🟢 Normal';
const rss = formatBytesMB(memoryUsage.rss);
const heapUsed = formatBytesMB(memoryUsage.heapUsed);
const heapTotal = formatBytesMB(memoryUsage.heapTotal);
const external = formatBytesMB(memoryUsage.external);
// @ts-ignore
return {
type: 17,
accent_color: 0xFF69B4,
components: [
{
type: 10,
content: '## 🌸 Panel de Administración - Gemini-chan (Actualizado)'
},
{
type: 10,
content: '-# Estadísticas refrescadas automáticamente.'
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: `🔄 **Conversaciones Activas:** ${stats.activeConversations}` }
],
accessory: {
type: 2,
style: 1,
emoji: "🧹",
label: 'Limpiar Cache',
custom_id: 'ai_clear_cache'
}
},
{
type: 9,
components: [
{ type: 10, content: `📊 **Requests en Cola:** ${stats.queueLength} | **Estado:** ${queueStatus}` }
],
accessory: {
type: 2,
style: 1,
emoji: "🔄",
label: 'Refrescar Stats',
custom_id: 'ai_refresh_stats'
}
},
{
type: 9,
components: [
{ type: 10, content: `⏱️ **Total Requests:** ${stats.totalRequests} | **Uptime:** ${formatUptime(uptime)}` }
],
accessory: {
type: 2,
style: 2,
emoji: "🔧",
label: 'Configuración',
custom_id: 'ai_config'
}
},
{ type: 14, divider: true, spacing: 1 },
{
type: 10,
content: ` ## 🧠 Uso de Memoria del Sistema IA
\`\`\`
┌─────────────────┬──────────────┬──────────┐
│ Memory Type │ Usage │ Status │
├─────────────────┼──────────────┼──────────┤
│ RSS │ ${rss.padEnd(12)} │ Normal │
│ Heap Used │ ${heapUsed.padEnd(12)}${memoryStatus.padEnd(8)}
│ Heap Total │ ${heapTotal.padEnd(12)} │ Normal │
│ External │ ${external.padEnd(12)} │ Normal │
└─────────────────┴──────────────┴──────────┘
📈 Configuración Actual:
• Rate Limit: 20 req/min por usuario
• Cooldown: 3 segundos entre requests
• Max Tokens: 1M entrada / 8K salida
• Max Concurrent: 3 requests simultáneos
• Modelo: gemini-1.5-flash
\`\`\`
🔄 Última actualización: ${ts} UTC
⚠️ **Nota:** El sistema se resetea automáticamente cada 30 minutos para optimizar memoria.`
},
{ type: 14, divider: true, spacing: 1 },
{
type: 9,
components: [
{ type: 10, content: "<:Sup_urg:1420535068056748042> **REINICIAR** todo el sistema de IA (limpia cache, conversaciones y estadísticas)" }
],
accessory: {
type: 2,
style: 4,
emoji: "⚠️",
label: 'RESET COMPLETO',
custom_id: 'ai_full_reset'
}
}
]
};
}
const OWNER_ID = '327207082203938818'; // Solo el dueño puede usar este panel
export default {
customId: 'ai_refresh_stats',
run: async (interaction: ButtonInteraction) => {
// Verificar permisos de administrador
if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
return interaction.reply({
content: '❌ No tienes permisos de administrador para usar este botón.',
flags: MessageFlags.Ephemeral
// Verificar que sea el dueño del bot (CRÍTICO)
if (interaction.user.id !== OWNER_ID) {
return interaction.reply({
content: '❌ Solo el dueño del bot puede usar este panel administrativo.',
flags: MessageFlags.Ephemeral
});
}
try {
await interaction.deferUpdate();
// Refrescar y reconstruir el panel con datos actualizados
const refreshedPanel = buildRefreshedAIPanel();
await interaction.message.edit({ components: [refreshedPanel] });
logger.info(`Estadísticas de IA refrescadas por ${interaction.user.username} (${interaction.user.id})`);
} catch (error)
// Reconstruir panel principal con datos actualizados
const refreshedPanel = buildAIAdminPanel();
await interaction.message.edit({
components: [refreshedPanel],
flags: MessageFlags.IsComponentsV2
});
logger.info(`Estadísticas de IA refrescadas por el dueño ${interaction.user.username} (${interaction.user.id})`);
} catch (error) {
//@ts-ignore
logger.error('Error refrescando estadísticas de IA:', error);
if (!interaction.deferred && !interaction.replied) {