zelin-bot / src /humanizer.js
Z User
v5.8.9: Fix dedup, upgrade Qwen3.6-35B-A3B, humanize v2, personality v2, /messages endpoint
b4c36fb
/**
* ============================================
* 🎭 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(/<think[\s\S]*?<\/think>/gi, '').trim();
t = t.replace(/<thinking[\s\S]*?<\/thinking>/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, '\\$&');
}