Created
December 10, 2025 11:19
-
-
Save ekoeryanto/528ab7633a037c9522b417ae4c605b40 to your computer and use it in GitHub Desktop.
Simulasikan Pengetikan Manusia pada Chat Bot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * ========================================== | |
| * 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