Skip to content

Instantly share code, notes, and snippets.

@ekoeryanto
Created December 10, 2025 11:19
Show Gist options
  • Select an option

  • Save ekoeryanto/528ab7633a037c9522b417ae4c605b40 to your computer and use it in GitHub Desktop.

Select an option

Save ekoeryanto/528ab7633a037c9522b417ae4c605b40 to your computer and use it in GitHub Desktop.
Simulasikan Pengetikan Manusia pada Chat Bot
/**
* ==========================================
* HUMAN TYPING SIMULATION (INDONESIA)
* ==========================================
*/
const CONFIG = {
// Kecepatan Dasar (ms per karakter)
baseSpeed: { min: 40, max: 90 },
// Variasi ketidakstabilan jari (ms)
jitter: 20,
// Probabilitas Typo (0.05 = 5% kemungkinan salah ketik per huruf)
typoChance: 0.05,
// Waktu Jeda Khusus (ms)
delays: {
space: 20, // Jeda antar kata
comma: 200, // Koma (napas pendek)
sentenceEnd: 500, // Titik/Tanda Tanya (akhir kalimat)
paragraph: 800, // Enter
emojiSearch: 600, // Mencari emoji di menu
emojiSpam: 120, // Tap emoji yang sama berulang
typoRealization: 300, // Waktu sadar kalau salah ketik
backspace: 100 // Kecepatan tekan backspace
}
};
// --- UTILITIES ---
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
// Deteksi Emoji Modern
const isEmoji = (segment) => /\p{Extended_Pictographic}/u.test(segment);
// Karakter acak untuk simulasi typo (hanya huruf kecil)
const getRandomChar = () => String.fromCharCode(97 + Math.floor(Math.random() * 26));
/**
* Memecah string menjadi array visual (Graphemes)
* Penting agar Emoji kulit/bendera tidak pecah menjadi 2 karakter aneh.
*/
function splitTextSafe(text) {
if (typeof Intl !== 'undefined' && Intl.Segmenter) {
const segmenter = new Intl.Segmenter('id', { granularity: 'grapheme' });
return Array.from(segmenter.segment(text)).map(s => s.segment);
}
// Fallback untuk env lama (kurang akurat untuk emoji kompleks)
return [...text];
}
/**
* Hitung delay untuk karakter tertentu
*/
function calculateDelay(char, prevChar) {
// 1. Logic Emoji
if (isEmoji(char)) {
// Jika emoji sama dengan sebelumnya (spam), delay cepat
if (prevChar && char === prevChar) return CONFIG.delays.emojiSpam;
// Jika emoji baru, delay lama (cari menu)
return CONFIG.delays.emojiSearch;
}
// 2. Logic Dasar
let delay = randomInt(CONFIG.baseSpeed.min, CONFIG.baseSpeed.max);
// Tambahkan variasi (jitter)
delay += (Math.random() > 0.5 ? CONFIG.jitter : -CONFIG.jitter);
// 3. Logic Tanda Baca (Bahasa Indonesia)
switch (char) {
case ' ': delay += CONFIG.delays.space; break;
case ',': delay += CONFIG.delays.comma; break;
case '.':
case '?':
case '!': delay += CONFIG.delays.sentenceEnd; break;
case '\n': delay += CONFIG.delays.paragraph; break;
}
return delay;
}
/**
* ==========================================
* FUNGSI UTAMA: SIMULASI VISUAL
* ==========================================
* @param {string} message - Pesan yang akan diketik
* @param {function} onUpdate - Callback saat teks berubah (untuk update UI)
* @param {function} onSend - Callback saat selesai (untuk trigger kirim)
*/
async function simulateTyping(message, onUpdate, onSend) {
const segments = splitTextSafe(message);
let currentText = ""; // Teks yang sedang dibangun
let prevChar = null;
// Loop setiap karakter/emoji
for (let i = 0; i < segments.length; i++) {
const char = segments[i];
// --- LOGIC TYPO (SALAH KETIK) ---
// Hanya berlaku untuk huruf biasa, bukan emoji/spasi, dan probabilitas terpenuhi
if (!isEmoji(char) && char.trim() !== '' && Math.random() < CONFIG.typoChance) {
const wrongChar = getRandomChar();
// 1. Ketik huruf salah
await sleep(calculateDelay(wrongChar, prevChar));
onUpdate(currentText + wrongChar);
// 2. Jeda "Sadar Salah" (Reaction time)
await sleep(CONFIG.delays.typoRealization);
// 3. Hapus huruf salah (Backspace)
await sleep(CONFIG.delays.backspace);
onUpdate(currentText); // Kembali ke text sebelum salah
}
// --- KETIK HURUF BENAR ---
const delay = calculateDelay(char, prevChar);
await sleep(delay);
currentText += char;
onUpdate(currentText); // Update UI
prevChar = char;
}
// Selesai mengetik
if (onSend) onSend(currentText);
}
/**
* ==========================================
* CONTOH PENGGUNAAN (RUN ME!)
* ==========================================
*/
// Contoh Pesan dengan tanda baca & emoji kompleks
const pesanUser = "Halo min, paketnya kok belum sampai ya? πŸ€” Padahal butuh banget.. Tolong cek ya πŸ™πŸΌπŸ™πŸΌ";
console.log("--- MULAI SIMULASI ---\n");
// Jalankan Simulasi
simulateTyping(
pesanUser,
(text) => {
// FUNGSI UPDATE UI (Di sini kita pakai process.stdout untuk simulasi di terminal)
// Di Web/React: setTitle(text) atau element.innerText = text
// Clear console line & rewrite (agar terlihat animasi di satu baris)
if (typeof process !== 'undefined' && process.stdout) {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write("User: " + text);
} else {
// Fallback browser console (kurang bagus animasinya tapi jalan)
console.log(text);
}
},
(finalText) => {
// FUNGSI SAAT SELESAI (KIRIM)
console.log("\n\nβœ… [SYSTEM]: Pesan berhasil dikirim ke server.");
console.log(` Isi: "${finalText}"`);
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment