feat: simplify message replies and enhance modal error handling in AI commands

This commit is contained in:
2025-10-04 01:38:02 -05:00
parent 241c6a5d90
commit 8f1e96941a
2 changed files with 40 additions and 72 deletions

View File

@@ -1,6 +1,6 @@
import logger from "../../../core/lib/logger"; import logger from "../../../core/lib/logger";
import { CommandMessage } from "../../../core/types/commands"; import { CommandMessage } from "../../../core/types/commands";
import { TextChannel, DMChannel, ThreadChannel, EmbedBuilder, ChannelType } from "discord.js"; import { TextChannel, DMChannel, ThreadChannel, ChannelType } from "discord.js";
import { aiService } from "../../../core/services/AIService"; import { aiService } from "../../../core/services/AIService";
/** /**
@@ -124,17 +124,9 @@ export const command: CommandMessage = {
run: async (message, args) => { run: async (message, args) => {
// Validaciones básicas // Validaciones básicas
if (!args || args.length === 0) { if (!args || args.length === 0) {
const helpEmbed = new EmbedBuilder() await message.reply({
.setColor(0xFF69B4) content: '**Uso:** `ai <tu mensaje>`\n**Ejemplo:** `ai ¿Cómo funciona JavaScript?`\n**Límite:** 4000 caracteres máximo'
.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] });
return; return;
} }
@@ -145,9 +137,7 @@ export const command: CommandMessage = {
// Verificar tipo de canal // Verificar tipo de canal
const channel = message.channel as TextChannel | DMChannel | ThreadChannel; const channel = message.channel as TextChannel | DMChannel | ThreadChannel;
if (!channel || !('send' in channel)) { if (!channel || !('send' in channel)) {
await message.reply({ await message.reply({ content: "❌ **Error:** Este comando no se puede usar en este tipo de canal." });
content: "❌ **Error:** Este comando no se puede usar en este tipo de canal."
});
return; return;
} }
@@ -171,49 +161,26 @@ export const command: CommandMessage = {
{ meta } { meta }
); );
// Crear embed de respuesta mejorado // Discord limita el contenido a ~2000 caracteres
const embed = new EmbedBuilder() const MAX_CONTENT = 2000;
.setColor(0xFF69B4) if (aiResponse.length > MAX_CONTENT) {
.setTitle('🌸 Gemini-chan') const chunks = smartChunkText(aiResponse, MAX_CONTENT);
.setDescription(aiResponse)
.setFooter({
text: `Solicitado por ${message.author.username}`,
icon_url: 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);
for (let i = 0; i < chunks.length && i < 3; i++) {
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}`,
icon_url: message.author.displayAvatarURL({ forceStatic: false })
})
.setTimestamp();
for (let i = 0; i < chunks.length && i < 6; i++) {
if (i === 0) { if (i === 0) {
await message.reply({ embeds: [chunkEmbed] }); await message.reply({ content: chunks[i] });
} else { } else {
await channel.send({ embeds: [chunkEmbed] }); await channel.send({ content: chunks[i] });
// Pausa entre mensajes para evitar rate limits // Pausa entre mensajes para evitar rate limits
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 500));
} }
} }
if (chunks.length > 3) { if (chunks.length > 6) {
await channel.send({ await channel.send({ content: "⚠️ Nota: La respuesta fue truncada. Intenta una pregunta más específica." });
content: "⚠️ **Nota:** La respuesta fue truncada. Intenta preguntas más específicas."
});
} }
} else { } else {
await message.reply({ embeds: [embed] }); await message.reply({ content: aiResponse });
} }
// Log para monitoreo (solo en desarrollo) // Log para monitoreo (solo en desarrollo)
@@ -221,25 +188,9 @@ export const command: CommandMessage = {
const stats = aiService.getStats(); const stats = aiService.getStats();
logger.info(`AI Request completado - Usuario: ${userId}, Queue: ${stats.queueLength}, Conversaciones activas: ${stats.activeConversations}`); logger.info(`AI Request completado - Usuario: ${userId}, Queue: ${stats.queueLength}, Conversaciones activas: ${stats.activeConversations}`);
} }
} catch (error: any) { } catch (error: any) {
logger.error(`Error en comando AI para usuario ${userId}:`, error); logger.error(`Error en comando AI para usuario ${userId}:`, error);
await message.reply({ content: `❌ Error del Servicio de IA: ${error.message || 'Error desconocido del servicio'}` });
// 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 { } finally {
// Limpiar indicador de escritura // Limpiar indicador de escritura
clearInterval(typingInterval); clearInterval(typingInterval);

View File

@@ -1,6 +1,6 @@
import logger from "../../../core/lib/logger"; import logger from "../../../core/lib/logger";
import { CommandMessage } from "../../../core/types/commands"; import { CommandMessage } from "../../../core/types/commands";
import { ComponentType } from "discord-api-types/v10"; import { ComponentType, TextInputStyle } from "discord-api-types/v10";
import { hasManageGuildOrStaff } from "../../../core/lib/permissions"; import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
import { aiService } from "../../../core/services/AIService"; import { aiService } from "../../../core/services/AIService";
@@ -212,13 +212,30 @@ export const command: CommandMessage = {
const currentAiPrompt = currentServer?.aiRolePrompt ?? ''; const currentAiPrompt = currentServer?.aiRolePrompt ?? '';
const aiModal = { const aiModal = {
title: "🧠 Configurar AI Role Prompt", title: "🧠 Configurar AI Role Prompt",
custom_id: "ai_role_prompt_modal", customId: "ai_role_prompt_modal",
components: [ components: [
{ type: 1, components: [ { type: 4, custom_id: "ai_role_prompt_input", label: "Prompt de rol (opcional)", style: 2, placeholder: "Ej: Eres un asistente amistoso del servidor, responde en español, evita spoilers...", required: false, max_length: 1500, value: currentAiPrompt.slice(0, 1500) } ] } {
type: ComponentType.Label,
label: "Prompt de rol (opcional)",
component: {
type: ComponentType.TextInput,
customId: "ai_role_prompt_input",
style: TextInputStyle.Paragraph,
required: false,
placeholder: "Ej: Eres un asistente amistoso del servidor, responde en español, evita spoilers...",
maxLength: 1500,
value: currentAiPrompt.slice(0, 1500)
}
}
] ]
}; } as const;
await interaction.showModal(aiModal); try {
await interaction.showModal(aiModal);
} catch (err) {
try { await interaction.reply({ content: '❌ No se pudo abrir el modal de AI.', flags: 64 }); } catch {}
return;
}
try { try {
const modalInteraction = await interaction.awaitModalSubmit({ const modalInteraction = await interaction.awaitModalSubmit({
@@ -226,7 +243,7 @@ export const command: CommandMessage = {
filter: (m: any) => m.customId === 'ai_role_prompt_modal' && m.user.id === message.author.id filter: (m: any) => m.customId === 'ai_role_prompt_modal' && m.user.id === message.author.id
}); });
const newPromptRaw = modalInteraction.fields.getTextInputValue('ai_role_prompt_input') ?? ''; const newPromptRaw = modalInteraction.components.getTextInputValue('ai_role_prompt_input') ?? '';
const newPrompt = newPromptRaw.trim(); const newPrompt = newPromptRaw.trim();
const toSave: string | null = newPrompt.length > 0 ? newPrompt : null; const toSave: string | null = newPrompt.length > 0 ? newPrompt : null;