Kembali ke katalog plugin
ai
ai
Ngobrol dengan AI, ingat konteks percakapan
Chat with AI, remembers conversation context
plugins/ai/ai.js
Lokasi file di repo kana-md
3
Perintah termasuk alias
4.8 KB
Ukuran file sumber
4/11/2026
Update terakhir, commit 11aee2b
Daftar perintah
Ketuk tombol salin untuk menyalin perintah, lalu tempel di chat WhatsApp bot KANA.
.ai.kana.chatCara pakai
.ai <pesan>
Contoh
.ai jelaskan fotosintesis dengan singkat
Kode sumber plugin
Cara pasang: simpan file ini ke folder plugins/ai/ di bot KANA kamu, lalu restart bot. Pemilik source code lain bisa mengambil update plugin terbaru dari halaman ini kapan saja.
// .ai: ngobrol dengan AI memakai memori percakapan per pengguna.
//
// Perubahan perilaku (2026-11-04, Fase 7): versi lama hanya jalan bila
// rudder.chat dikonfigurasi owner (opt-in cookie) dan langsung menolak
// bila belum. Versi ini memakai src/services/ai-provider.js: rudder
// tetap diprioritaskan bila terkonfigurasi, selain itu otomatis jatuh
// ke Pollinations teks publik tanpa key (gratis, sudah dipakai proyek
// untuk gambar). Memori 6 pasang terakhir per pengguna disimpan lewat
// src/services/ai-memory.js dan bisa direset dengan .aireset.
import { getAiProvider } from '../../src/services/ai-provider.js'
import { getMemory, appendTurn } from '../../src/services/ai-memory.js'
import { sendButtons } from '../../src/ui/buttons.js'
export const config = {
name: 'ai',
alias: ['kana', 'chat'],
category: 'ai',
description: { id: 'Ngobrol dengan AI, ingat konteks percakapan', en: 'Chat with AI, remembers conversation context' },
usage: '.ai <pesan>',
example: '.ai jelaskan fotosintesis dengan singkat',
cooldown: 10,
limit: 5,
}
export const i18n = {
id: {
ai: {
needText: 'Ketik pesannya dulu yaa, contoh: .ai jelaskan fotosintesis dengan singkat~',
notConfigured:
'Mouu, fitur AI belum dikonfigurasi sama Master nih (╥﹏╥) Provider AI belum tersedia, jadi KANA belum bisa ngobrol AI dulu ya~',
thinking: 'Hmm, KANA tanya ke AI dulu ya... {mood}',
failed: 'Mouu, AI-nya gagal jawab: {message}. Coba lagi sebentar lagi ya~ (╥﹏╥)',
failedNetwork: 'Mouu, KANA gagal menghubungi AI-nya nih: {message}. Jaringannya lagi bermasalah, coba lagi nanti ya~ (╥﹏╥) 💦',
empty: 'Ehh? AI-nya jawab kosong. Coba tanya dengan kalimat lain ya~',
truncated: '\n\n(Dipotong KANA biar nggak kepanjangan~)',
btnReset: 'Lupa Percakapan',
btnMenu: 'Menu AI',
},
},
en: {
ai: {
needText: 'Type your message first, example: .ai explain photosynthesis briefly~',
notConfigured:
'Mouu, the AI feature is not configured by Master yet (╥﹏╥) No AI provider is available, so KANA cannot do AI chat yet~',
thinking: 'Hmm, let KANA ask the AI first... {mood}',
failed: 'Mouu, the AI failed to answer: {message}. Please try again in a bit~ (╥﹏╥)',
failedNetwork: 'Mouu, KANA could not reach the AI: {message}. The network is having trouble, try again later~ (╥﹏╥) 💦',
empty: 'Eh? The AI answered with nothing. Try rephrasing your question~',
truncated: '\n\n(Trimmed by KANA so it does not get too long~)',
btnReset: 'Forget Chat',
btnMenu: 'AI Menu',
},
},
}
const MAX_REPLY_CHARS = 3500
function isNetworkError(err) {
const msg = String(err?.message || '')
return (
err?.code === 'SOURCE_DOWN' ||
/fetch|network|timeout|timed out|ECONN|ENOTFOUND|socket|HTTP 5\d\d/i.test(msg)
)
}
export const test = { input: '.ai Apa ibu kota Jepang?', expect: /Tokyo/i }
export async function handler(ctx) {
const text = ctx.text.trim()
if (!text) return ctx.reply(ctx.t('ai.needText'))
const provider = getAiProvider()
if (!provider) return ctx.reply(ctx.t('ai.notConfigured'))
await ctx.reply(ctx.t('ai.thinking', { mood: ctx.say('mikir') }))
const botName = process.env.BOT_NAME || 'KANA'
const system =
ctx.lang === 'en'
? `You are ${botName}, a sweet and expressive WhatsApp assistant. Answer helpfully and concisely in the user's language. Keep the core information clear.`
: `Kamu adalah ${botName}, asisten WhatsApp yang manis dan ekspresif. Jawab dengan membantu dan ringkas dalam bahasa pengguna. Informasi inti harus tetap jelas.`
let memory = []
try {
memory = await getMemory(ctx.storage, ctx.senderDigits)
} catch {
memory = []
}
const messages = [
{ role: 'system', content: system },
...memory,
{ role: 'user', content: text },
]
let replyText
try {
replyText = String((await provider.chat(messages)) || '').trim()
} catch (err) {
if (err?.code === 'NOT_CONFIGURED') return ctx.reply(ctx.t('ai.notConfigured'))
if (isNetworkError(err)) return ctx.reply(ctx.t('ai.failedNetwork', { message: err.message }))
return ctx.reply(ctx.t('ai.failed', { message: err.message }))
}
if (!replyText) return ctx.reply(ctx.t('ai.empty'))
try {
await appendTurn(ctx.storage, ctx.senderDigits, text, replyText)
} catch {
// Memori gagal disimpan tidak boleh menggagalkan jawaban.
}
const trimmed =
replyText.length > MAX_REPLY_CHARS
? replyText.slice(0, MAX_REPLY_CHARS) + ctx.t('ai.truncated')
: replyText
return sendButtons(ctx.sock, ctx.chat, {
title: botName,
body: trimmed,
footer: ctx.t('menu.footer'),
replies: [
{ text: ctx.t('ai.btnReset'), id: '.aireset' },
{ text: ctx.t('ai.btnMenu'), id: '.menu ai' },
],
textFallback: trimmed,
})
}
