2025-10-02 20:11:33 -05:00
|
|
|
import logger from "../../../core/lib/logger";
|
2025-10-02 21:52:08 -05:00
|
|
|
import { CommandMessage } from "../../../core/types/commands";
|
|
|
|
|
import { TextChannel, DMChannel, NewsChannel, ThreadChannel, EmbedBuilder } from "discord.js";
|
|
|
|
|
import { aiService } from "../../../core/services/AIService";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dividir texto de forma inteligente preservando markdown
|
|
|
|
|
*/
|
|
|
|
|
function smartChunkText(text: string, maxLength: number): string[] {
|
|
|
|
|
if (text.length <= maxLength) return [text];
|
|
|
|
|
|
|
|
|
|
const chunks: string[] = [];
|
|
|
|
|
let currentChunk = '';
|
|
|
|
|
const lines = text.split('\n');
|
|
|
|
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
// Si agregar esta línea excede el límite
|
|
|
|
|
if (currentChunk.length + line.length + 1 > maxLength) {
|
|
|
|
|
if (currentChunk) {
|
|
|
|
|
chunks.push(currentChunk.trim());
|
|
|
|
|
currentChunk = '';
|
|
|
|
|
}
|
2025-09-21 15:10:28 -05:00
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
// Si la línea misma es muy larga, dividirla por palabras
|
|
|
|
|
if (line.length > maxLength) {
|
|
|
|
|
const words = line.split(' ');
|
|
|
|
|
let wordChunk = '';
|
|
|
|
|
|
|
|
|
|
for (const word of words) {
|
|
|
|
|
if (wordChunk.length + word.length + 1 > maxLength) {
|
|
|
|
|
if (wordChunk) {
|
|
|
|
|
chunks.push(wordChunk.trim());
|
|
|
|
|
wordChunk = '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
wordChunk += (wordChunk ? ' ' : '') + word;
|
|
|
|
|
}
|
2025-09-21 15:10:28 -05:00
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
if (wordChunk) {
|
|
|
|
|
currentChunk = wordChunk;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
currentChunk = line;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
currentChunk += (currentChunk ? '\n' : '') + line;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-21 15:25:20 -05:00
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
if (currentChunk) {
|
|
|
|
|
chunks.push(currentChunk.trim());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return chunks;
|
|
|
|
|
}
|
2025-09-21 15:10:28 -05:00
|
|
|
|
|
|
|
|
export const command: CommandMessage = {
|
|
|
|
|
name: 'ai',
|
|
|
|
|
type: "message",
|
|
|
|
|
aliases: ['chat', 'gemini'],
|
2025-10-02 21:52:08 -05:00
|
|
|
cooldown: 2, // Reducido porque el servicio maneja su propio rate limiting
|
|
|
|
|
description: 'Chatea con la IA (Gemini) de forma estable y escalable.',
|
2025-09-26 06:38:09 -05:00
|
|
|
category: 'IA',
|
|
|
|
|
usage: 'ai <mensaje>',
|
2025-09-21 15:10:28 -05:00
|
|
|
run: async (message, args) => {
|
2025-10-02 21:52:08 -05:00
|
|
|
// Validaciones básicas
|
2025-09-21 15:10:28 -05:00
|
|
|
if (!args || args.length === 0) {
|
2025-10-02 21:52:08 -05:00
|
|
|
const helpEmbed = new EmbedBuilder()
|
|
|
|
|
.setColor(0xFF69B4)
|
|
|
|
|
.setTitle('❌ Error: Mensaje requerido')
|
|
|
|
|
.setDescription(
|
|
|
|
|
'**Uso:** `ai <tu mensaje>`\n' +
|
|
|
|
|
'**Ejemplo:** `ai ¿Cómo funciona JavaScript?`\n' +
|
|
|
|
|
'**Límite:** 4000 caracteres máximo'
|
|
|
|
|
)
|
|
|
|
|
.setFooter({ text: 'AI Chat mejorado con Gemini 2.5 Flash' });
|
|
|
|
|
|
|
|
|
|
await message.reply({ embeds: [helpEmbed] });
|
2025-09-21 15:10:28 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const prompt = args.join(' ');
|
2025-09-21 15:25:20 -05:00
|
|
|
const userId = message.author.id;
|
2025-10-02 21:52:08 -05:00
|
|
|
const guildId = message.guild?.id;
|
2025-09-21 15:10:28 -05:00
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
// Verificar tipo de canal
|
2025-09-21 15:10:28 -05:00
|
|
|
const channel = message.channel as TextChannel | DMChannel | NewsChannel | ThreadChannel;
|
|
|
|
|
if (!channel || !('send' in channel)) {
|
|
|
|
|
await message.reply({
|
|
|
|
|
content: "❌ **Error:** Este comando no se puede usar en este tipo de canal."
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
// Indicador de escritura mejorado
|
|
|
|
|
const typingInterval = setInterval(() => {
|
|
|
|
|
channel.sendTyping().catch(() => {});
|
|
|
|
|
}, 5000);
|
2025-09-21 15:25:20 -05:00
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
try {
|
|
|
|
|
// Usar el servicio mejorado con manejo de prioridad
|
|
|
|
|
const priority = message.member?.permissions.has('Administrator') ? 'high' : 'normal';
|
|
|
|
|
|
|
|
|
|
const aiResponse = await aiService.processAIRequest(
|
|
|
|
|
userId,
|
|
|
|
|
prompt,
|
|
|
|
|
guildId,
|
|
|
|
|
priority
|
2025-09-21 15:10:28 -05:00
|
|
|
);
|
|
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
// Crear embed de respuesta mejorado
|
|
|
|
|
const embed = new EmbedBuilder()
|
|
|
|
|
.setColor(0xFF69B4)
|
|
|
|
|
.setTitle('🌸 Gemini-chan')
|
|
|
|
|
.setDescription(aiResponse)
|
|
|
|
|
.setFooter({
|
|
|
|
|
text: `Solicitado por ${message.author.username}`,
|
|
|
|
|
iconURL: message.author.displayAvatarURL({ forceStatic: false })
|
|
|
|
|
})
|
|
|
|
|
.setTimestamp();
|
|
|
|
|
|
|
|
|
|
// Manejar respuestas largas de forma inteligente
|
|
|
|
|
if (aiResponse.length > 4000) {
|
|
|
|
|
// Dividir en chunks preservando markdown
|
|
|
|
|
const chunks = smartChunkText(aiResponse, 4000);
|
2025-09-21 15:10:28 -05:00
|
|
|
|
|
|
|
|
for (let i = 0; i < chunks.length && i < 3; i++) {
|
2025-10-02 21:52:08 -05:00
|
|
|
const chunkEmbed = new EmbedBuilder()
|
|
|
|
|
.setColor(0xFF69B4)
|
|
|
|
|
.setTitle(i === 0 ? '🌸 Gemini-chan' : `🌸 Gemini-chan (${i + 1}/${chunks.length})`)
|
|
|
|
|
.setDescription(chunks[i])
|
|
|
|
|
.setFooter({
|
|
|
|
|
text: `Solicitado por ${message.author.username} | Parte ${i + 1}`,
|
|
|
|
|
iconURL: message.author.displayAvatarURL({ forceStatic: false })
|
|
|
|
|
})
|
|
|
|
|
.setTimestamp();
|
2025-09-21 15:10:28 -05:00
|
|
|
|
|
|
|
|
if (i === 0) {
|
2025-10-02 21:52:08 -05:00
|
|
|
await message.reply({ embeds: [chunkEmbed] });
|
2025-09-21 15:10:28 -05:00
|
|
|
} else {
|
2025-10-02 21:52:08 -05:00
|
|
|
await channel.send({ embeds: [chunkEmbed] });
|
|
|
|
|
// Pausa entre mensajes para evitar rate limits
|
2025-09-21 15:10:28 -05:00
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (chunks.length > 3) {
|
|
|
|
|
await channel.send({
|
2025-10-02 21:52:08 -05:00
|
|
|
content: "⚠️ **Nota:** La respuesta fue truncada. Intenta preguntas más específicas."
|
2025-09-21 15:10:28 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await message.reply({ embeds: [embed] });
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
// Log para monitoreo (solo en desarrollo)
|
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
const stats = aiService.getStats();
|
|
|
|
|
logger.info(`AI Request completado - Usuario: ${userId}, Queue: ${stats.queueLength}, Conversaciones activas: ${stats.activeConversations}`);
|
2025-09-21 15:10:28 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-02 21:52:08 -05:00
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`Error en comando AI para usuario ${userId}:`, error);
|
|
|
|
|
|
|
|
|
|
// Crear embed de error informativo
|
|
|
|
|
const errorEmbed = new EmbedBuilder()
|
|
|
|
|
.setColor(0xFF4444)
|
|
|
|
|
.setTitle('❌ Error del Servicio de IA')
|
|
|
|
|
.setDescription(error.message || 'Error desconocido del servicio')
|
|
|
|
|
.addFields({
|
|
|
|
|
name: '💡 Consejos',
|
|
|
|
|
value: '• Verifica que tu mensaje no sea demasiado largo\n' +
|
|
|
|
|
'• Espera unos segundos entre consultas\n' +
|
|
|
|
|
'• Evita contenido inapropiado'
|
|
|
|
|
})
|
|
|
|
|
.setFooter({ text: 'Si el problema persiste, contacta a un administrador' })
|
|
|
|
|
.setTimestamp();
|
|
|
|
|
|
|
|
|
|
await message.reply({ embeds: [errorEmbed] });
|
|
|
|
|
} finally {
|
|
|
|
|
// Limpiar indicador de escritura
|
|
|
|
|
clearInterval(typingInterval);
|
2025-09-21 15:10:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|