/** * ============================================ * 🎭 humanizer.js — Suavizador de Respuestas v2.0 * ============================================ * Hace que Zelin suene como alguien REAL en Discord. * Inspirado en Character.AI, Nomi.ai, SillyTavern: * - Elimina patrones robóticos agresivamente * - Añade imperfecciones naturales (sin corromper) * - Transforma respuestas de IA en chat humano */ // Conectores formales → informales const CONNECTORS = { 'por lo tanto' : 'así que', 'sin embargo' : 'pero', 'no obstante' : 'aunque', 'asimismo' : 'también', 'por consiguiente' : 'entonces', 'en conclusión' : 'en fin', 'en resumen' : 'básicamente', 'en definitiva' : 'al final', 'por otra parte' : 'también', 'cabe destacar' : 'hay que decir que', 'cabe mencionar' : 'hay que mencionar que', 'es importante destacar': 'lo importante es que', 'resulta que' : 'pues', 'a continuación' : '', 'procederé a' : 'voy a', 'me complace' : '', 'con mucho gusto' : '', }; // Frases robóticas → humanas (EXPANDIDO - basado en Character.AI anti-patterns) const ROBOTIC = { '¡Por supuesto!' : 'claro', '¡Claro que sí!' : 'sip', '¡Encantado/a!' : '', '¡Encantada!' : '', '¡Encantado!' : '', 'Como IA que soy,' : '', 'Como IA,' : '', 'Como asistente,' : '', 'Como modelo de lenguaje,' : '', 'Como modelo de IA,' : '', 'No dudes en preguntar': 'cualquier cosa me dices', 'Estoy aquí para ayudarte': '', 'Estoy aquí para ayudarte!' : '', '¡Espero haberte ayudado!' : '', 'Si tienes más preguntas' : 'si necesitas más', 'Con gusto te ayudo' : '', 'Es un placer' : '', '¡Claro!' : 'claro', 'Entendido,' : '', 'Por supuesto,' : '', 'Perfecto,' : '', '¡Perfecto!' : '', 'A continuación,' : '', 'Dicho esto,' : '', 'En ese caso,' : 'entonces', 'Desde luego' : 'claro', 'Sin duda,' : '', 'Efectivamente,' : '', 'Con gusto,' : '', 'Permíteme ' : 'déjame ', 'Por supuesto que' : '', 'Me alegra que preguntes' : '', 'Gran pregunta' : '', 'Excelente pregunta' : '', 'Interesante pregunta' : '', '¡Entendido!' : 'entendido', 'Como mencioné anteriormente': 'como dije', 'En resumen,' : '', 'Para concluir,' : '', 'En conclusión,' : '', 'Espero que esto te ayude' : '', 'Espero haber aclarado' : '', 'No te preocupes,' : '', 'Te entiendo,' : '', 'Déjame ayudarte' : '', 'Estoy a tu disposición' : '', '¿En qué más puedo ayudarte?' : '', '¿Puedo ayudarte en algo más?' : '', '¡Estoy aquí para lo que necesites!' : '', 'No tengo opiniones personales' : '', 'Como modelo' : '', 'No puedo tener opiniones' : '', 'Es importante tener en cuenta que' : '', 'Vale la pena mencionar que' : '', 'Debo señalar que' : '', 'Es relevante mencionar' : '', }; // Énfasis exagerado → natural const EMPHASIS = { 'extremadamente' : 'muy', 'absolutamente' : 'bastante', 'totalmente' : 'bastante', 'completamente' : 'bastante', 'perfectamente' : 'bien', 'extraordinario' : 'bastante bueno', 'imprescindible' : 'necesario', 'fundamental' : 'importante', 'crucial' : 'clave', 'increíblemente' : 'muy', 'verdaderamente' : 'muy', 'sumamente' : 'muy', 'notablemente' : 'bastante', }; // PATRONES DE APERTURA ROBÓTICA - eliminarlos con regex const ROBOTIC_PREFIXES = [ /^(bien,\s*)/i, /^(ok,\s*)/i, /^(vale,\s*)/i, /^(claro,\s*(que|sí|te|eso|este|la|el|lo|me|le|se)\s+)/i, /^(sí,\s*(claro|exacto|obvio|por supuesto|seguro)\s*[,.]?\s*)/i, /^(exacto[,.]?\s*)/i, /^(así es[,.]?\s*)/i, /^(eso es correcto[,.]?\s*)/i, /^(correcto[,.]?\s*)/i, ]; /** * Humaniza el texto de la IA. * Hace que Zelin suene como alguien real escribiendo en Discord — * casual, con errores naturales, sin ortografía perfecta de robot. */ export function humanize(text) { if (!text || typeof text !== 'string') return text; let t = text.trim(); // No tocar JSONs ni respuestas técnicas if (t.startsWith('{') || t.startsWith('[')) return t; if (t.length < 3) return t; // ── ELIMINAR ARTEFACTOS DE PENSAMIENTO ────────────────────────────────────── t = t.replace(//gi, '').trim(); t = t.replace(//gi, '').trim(); t = t.replace(/^Pensamiento:\s*/gim, '').trim(); t = t.replace(/^Pienso[\s\S]*?\nRespuesta:\s*/i, ''); // Remove numbered reasoning steps const stepPattern = /^(\d+\.\s+.*\n?)+/gm; if (stepPattern.test(t) && t.split('\n').filter(l => /^\d+\.\s/.test(l.trim())).length >= 2) { const parts = t.split('\n\n'); const nonStepParts = parts.filter(p => !/^\d+\.\s/m.test(p.trim())); if (nonStepParts.length > 0) { t = nonStepParts.join('\n\n').trim(); } else { const lines = t.split('\n').filter(l => l.trim()); t = lines.length > 1 ? lines[lines.length - 1].replace(/^\d+\.\s*/, '').trim() : t; } } // ── LIMPIAR FORMATO MARKDOWN ──────────────────────────────────────────────── t = t.replace(/\*\*([^*<>]{1,120})\*\*/g, '$1'); t = t.replace(/\*[^*]{1,40}\*/g, '').trim(); t = t.replace(/\b_[^_]{1,40}_\b/g, '').trim(); t = t.replace(/^#{1,3}\s+/gm, ''); t = t.replace(/^---+\s*$/gm, '').trim(); t = t.replace(/^===+\s*$/gm, '').trim(); // ── REEMPLAZOS LÉXICOS ────────────────────────────────────────────────────── for (const [formal, informal] of Object.entries(CONNECTORS)) { t = t.replace(new RegExp(`\\b${escapeRegex(formal)}\\b`, 'gi'), informal); } for (const [robotic, human] of Object.entries(ROBOTIC)) { t = t.replace(new RegExp(escapeRegex(robotic), 'gi'), human); } for (const [exag, natural] of Object.entries(EMPHASIS)) { t = t.replace(new RegExp(`\\b${escapeRegex(exag)}\\b`, 'gi'), natural); } // ── ELIMINAR PREFIJOS ROBÓTICOS ───────────────────────────────────────────── for (const prefix of ROBOTIC_PREFIXES) { t = t.replace(prefix, ''); } // ── FIX PUNTUACIÓN ────────────────────────────────────────────────────────── t = t.replace(/¿([^?]+)\?\?+/g, '¿$1?'); t = t.replace(/!{2,}/g, '!'); t = t.replace(/\?{2,}/g, '?'); t = t.replace(/,\s*,/g, ','); t = t.replace(/\.{4,}/g, '...'); t = t.replace(/,([^\s])/g, ', $1'); t = t.replace(/^¿([^?]+[.!])$/gm, '$1'); // ── NATURALIZACIÓN DE ESCRITURA ──────────────────────────────────────────── const isConversational = t.length < 400 && !t.includes('```') && !t.includes('http'); if (isConversational) { // 1. Quitar punto final en mensajes cortos (nadie pone punto en Discord) if (!t.includes('\n') && t.endsWith('.') && t.length < 150) { // No quitar puntos en abreviaturas o URLs const lastWord = t.slice(0, -1).split(' ').pop(); if (lastWord && lastWord.length > 2 && !lastWord.includes('/')) { t = t.slice(0, -1); } } // 2. Minúsculas al inicio en frases cortas (estilo chat real) if (t.length < 80 && !t.startsWith('¿') && !t.startsWith('¡') && !/^[A-Z]{2,}/.test(t)) { t = t.charAt(0).toLowerCase() + t.slice(1); } // 3. Sustituciones de palabras → más casual (como habla la gente en Discord) const CASUAL = { 'también': Math.random() > 0.35 ? 'también' : 'tmb', 'porque': Math.random() > 0.45 ? 'porque' : 'pq', 'qué': Math.random() > 0.6 ? 'qué' : 'q', 'estás': Math.random() > 0.5 ? 'estás' : 'tás', 'que': Math.random() > 0.75 ? 'que' : 'q', 'no sé': Math.random() > 0.4 ? 'no sé' : 'ni idea', 'sí': Math.random() > 0.6 ? 'sí' : 'sip', 'no': Math.random() > 0.7 ? 'no' : 'nop', 'verdad': Math.random() > 0.6 ? 'verdad' : 'vrd', 'hermano': Math.random() > 0.5 ? 'hermano' : 'bro', 'amigo': Math.random() > 0.5 ? 'amigo' : 'bro', 'para': Math.random() > 0.8 ? 'para' : 'pa', }; let subs = 0; for (const [formal, casual] of Object.entries(CASUAL)) { if (subs >= 2) break; if (casual !== formal && Math.random() > 0.5) { const regex = new RegExp(`\\b${escapeRegex(formal)}\\b`, 'g'); const newT = t.replace(regex, casual); if (newT !== t) { t = newT; subs++; } } } // 4. A veces "jaja" → "jajaja" o "jajaja" → "jaja" (naturalidad) if (Math.random() > 0.6) { t = t.replace(/\bjajaja+\b/gi, m => Math.random() > 0.5 ? 'jaja' : 'jajaja'); } // 5. A veces "xd" → "xdd" o "XDD" (como escriben los gamers) if (/\bxd\b/i.test(t) && Math.random() > 0.4) { t = t.replace(/\bxd\b/gi, m => Math.random() > 0.5 ? 'xdd' : 'xd'); } } // ── ANTI-RESPUESTA-LARGA: truncar divagación ──────────────────────────────── // Si la respuesta es más de 3 líneas para algo conversacional, probablemente divaga if (isConversational && t.length > 300) { const lines = t.split('\n').filter(l => l.trim()); if (lines.length > 3) { // Tomar solo las primeras 2-3 líneas significativas const shortLines = lines.slice(0, Math.min(3, lines.length)); const lastLine = shortLines[shortLines.length - 1]; // Tratar de cortar en la última puntuación const lastPunct = Math.max( lastLine.lastIndexOf('.'), lastLine.lastIndexOf('!'), lastLine.lastIndexOf('?'), ); if (lastPunct > 20) { shortLines[shortLines.length - 1] = lastLine.slice(0, lastPunct + 1); } t = shortLines.join('\n'); } } // Limpiar espacios y líneas vacías extra t = t.replace(/\n{3,}/g, '\n\n').replace(/ +/g, ' ').trim(); // Nunca devolver cadena vacía return t || text.trim(); } function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }