Chat Rooms — Siapa Memegang Percakapan
Permintaan dari rekaman Jam: agen perlu melihat percakapan mana yang dipegang manusia dan mana yang dipegang AI, serta bisa menandai percakapan dengan nama yang mereka tentukan sendiri.
❌ Alur LAMA
Akibat paling serius: AI ikut membalas di percakapan yang sudah ditangani agen manusia, karena AI tidak punya cara mengetahui bahwa ada manusia yang sudah menjawab.
✅ Alur BARU
Tiga lapisan baru: atribusi pengirim, inferensi pemegang, dan takeover eksplisit — plus label percakapan.
Perbandingan berdampingan
| Aspek | ❌ Lama | ✅ Baru |
|---|---|---|
| Balasan agen dicatat lokal | ❌ Tidak | ✅ Ya, dengan sent_by=human + id agen |
| AI tahu ada agen yang menjawab | ❌ Tidak | ✅ Ya |
| "Stop AI When Human Chat" di WhatsApp | ❌ Mati total | ✅ Berfungsi |
| Penanda pemegang di daftar percakapan | ❌ Tidak ada | ✅ AI / nama agen / Bot / Belum dibalas |
| Ketiadaan data | Diklaim "AI" | Dilaporkan "Belum dibalas" |
| Ambil alih percakapan | ❌ Tidak ada | ✅ contacts.handled_by, otomatis saat agen membalas |
| Label percakapan | ❌ Tidak ada | ✅ Global, per nomor kontak |
| Fokus tab saat chat baru masuk | — | Tab Queue menarik fokus, tapi hanya bila tidak ada percakapan terbuka |
Tiga temuan yang membentuk implementasinya
Menentukan siapa yang membalas ternyata bagian tersulit. Tiga fakta dari API Jasnita, semuanya diverifikasi langsung:
Pemakaian last_chat_by sebagai pintasan akan menyembunyikan justru percakapan yang sedang menunggu jawaban lanjutan — pelanggan sudah membalas setelah agen menjawab, jadi nilainya kembali 0 padahal percakapannya aktif.
Biaya panggilan API dan cara menekannya
Menentukan pemegang butuh get-detail per baris percakapan. Kalau dipanggil setiap polling 10 detik untuk 20 baris, itu 120 panggilan per menit per agen.
Selama tidak ada pesan baru di sebuah percakapan, pemegangnya tidak mungkin berubah — jadi cache boleh bertahan lama.
Takeover: eksplisit menimpa inferensi
| Takeover eksplisit | Toggle "Stop AI When Human Chat" | |
|---|---|---|
| Apa yang diurus | Keputusan operator | Takeover implisit — agen membalas tanpa menekan tombol apa pun |
| Bisa dimatikan | ❌ Tidak | ✅ Ya, per AI Agent |
| Disimpan di | contacts.handled_by | Perilaku runtime dari riwayat pesan |
| Dipicu oleh | Mengirim pesan manual, atau tombol ambil alih | Adanya pesan human setelah balasan bot terakhir |
Mengirim pesan manual mengklaim percakapan otomatis — tidak ada langkah tambahan yang harus diingat agen.
Label: kenapa menempel ke nomor kontak
Nama label juga unik case-insensitive: dua agen yang mengetik "Sudah dibalas" harus berakhir di label yang sama, bukan dua label kembar yang membuat penyaringan setengah-setengah.
Fokus tab: keputusan UX yang disengaja
Menarik agen keluar dari balasan yang sedang diketik lebih merugikan daripada membantu.
Yang ikut lahir dari perubahan ini
| Objek baru | Fungsi |
|---|---|
whatsapp_messages.sent_by | ai | human | system |
whatsapp_messages.sent_by_user_id | Agen yang mengetik (terindeks, tanpa FK lintas-schema) |
Tabel chat_labels | Label global tim |
Tabel chat_label_contact | Pivot label ↔ kontak, unik per pasangan |
ChatHandlerResolver | Inferensi pemegang |
ChatTakeoverService | Takeover eksplisit |
ChatLabelService | Pemasangan label |
| Resource Chat Labels | Kelola label di /admin/chat-labels |
Izin *:ChatLabel | admin penuh; petugas tanpa hapus |
Detail operasionalnya ada di Alur Chat Rooms.