Add remaining project files (exclude ignored folders)

This commit is contained in:
WD - Dev
2025-07-05 15:11:40 +07:00
parent a96eb2b958
commit b440b80882
4697 changed files with 1365702 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
2025-05-25 09:22:06 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-25 09:22:16 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-25 09:22:26 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-25 09:22:36 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-30 22:04:51 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-30 22:05:01 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-30 22:05:11 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
2025-05-30 22:05:21 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+111
View File
@@ -0,0 +1,111 @@
<?php
include __DIR__ . "/../../config/whatsapp_api.php";
include __DIR__ . "/respon_ai.php";
// Setup error reporting untuk debugging
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Simpan log raw input untuk debug paling awal
// file_put_contents(__DIR__ . "/debug_raw_input.log", date('Y-m-d H:i:s') . " Input:\n" . print_r(file_get_contents('php://input'), true) . "\n", FILE_APPEND);
try {
// Ambil dan decode input JSON
$data = file_get_contents('php://input');
$json_data = json_decode($data, true);
if (!isset($json_data['results']['apiKey'])) {
http_response_code(400);
echo "apiKey tidak ditemukan.";
exit;
}
$apiKey = $json_data['results']['apiKey'];
// file_put_contents(__DIR__ . "/debug_apikey.log", date('Y-m-d H:i:s') . " apiKey: $apiKey\n", FILE_APPEND);
if (
isset($json_data['type']) && $json_data['type'] === 'message' &&
isset($json_data['results']['fromMe']) && $json_data['results']['fromMe'] === false &&
isset($json_data['results']['isGroupMsg']) && $json_data['results']['isGroupMsg'] === false
) {
$sender = $json_data['results']['sender'] ?? 'Tidak diketahui';
$sender = str_replace("@s.whatsapp.net", "", $sender);
$body = $json_data['results']['body'] ?? '';
// file_put_contents(__DIR__ . "/debug_sender_body.log", date('Y-m-d H:i:s') . " Sender: $sender, Body: $body\n", FILE_APPEND);
$filtered = [
'sender' => $sender,
'body' => $body,
'apiKey' => $apiKey
];
$log_file = __DIR__ . "/" . $apiKey . ".log";
$log_entry = "[" . date('Y-m-d H:i:s') . "] Masuk: " . print_r($filtered, true);
// Pastikan kelas ResponAI ada
if (!class_exists('ResponAI')) {
file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Class ResponAI tidak ditemukan!\n", FILE_APPEND);
http_response_code(500);
exit("Server error: ResponAI class missing");
}
$ResAI = new ResponAI();
// Tangkap exception saat proses respon_chat
try {
$Respon = $ResAI->respon_chat($body, $sender, $apiKey);
} catch (Exception $e) {
file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Exception ResponAI: " . $e->getMessage() . "\n", FILE_APPEND);
http_response_code(500);
exit("Server error: gagal proses AI");
}
// file_put_contents(__DIR__ . "/step2_respon_ai.log", date('Y-m-d H:i:s') . " " . print_r($Respon, true), FILE_APPEND);
if (!empty($Respon['id']) && !empty($Respon['nomor'])) {
if (!class_exists('PesanWA')) {
file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Class PesanWA tidak ditemukan!\n", FILE_APPEND);
http_response_code(500);
exit("Server error: PesanWA class missing");
}
$API_WA = new PesanWA();
try {
$send_result = $API_WA->Send_Pesan($Respon['id'], 'sendMessage', array($Respon['nomor']), $Respon['respon']);
} catch (Exception $e) {
file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Exception Send_Pesan: " . $e->getMessage() . "\n", FILE_APPEND);
http_response_code(500);
exit("Server error: gagal kirim pesan");
}
// file_put_contents(__DIR__ . "/step3_send_pesan.log", date('Y-m-d H:i:s') . " Kirim pesan ke: {$Respon['nomor']}\nResponse Send_Pesan: " . print_r($send_result, true), FILE_APPEND);
$log_entry .= "[" . date('Y-m-d H:i:s') . "] Balasan AI: " . print_r($Respon, true) . PHP_EOL;
// file_put_contents($log_file, $log_entry, FILE_APPEND);
http_response_code(200);
exit;
} else {
// file_put_contents(__DIR__ . "/error_send_pesan.log", date('Y-m-d H:i:s') . " Gagal kirim pesan, id atau nomor kosong: " . print_r($Respon, true) . "\n", FILE_APPEND);
http_response_code(500);
echo "Gagal mengirim pesan: data tidak lengkap.";
exit;
}
} else {
http_response_code(400);
echo "Data tidak valid atau bukan pesan yang diinginkan.";
exit;
}
} catch (Throwable $t) {
// Tangkap semua error fatal / throwable
file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Fatal error: " . $t->getMessage() . "\n", FILE_APPEND);
http_response_code(500);
echo "Server error: " . $t->getMessage();
exit;
}
+276
View File
@@ -0,0 +1,276 @@
<?php
class ResponAI {
private function connectDB() {
include __DIR__ . "/../../config/connect.php"; // Harus mengisi $pdo
return $pdo;
}
private function deteksi_topik($message) {
$rules = [
'gangguan' => ['lambat', 'tidak konek', 'jaringan', 'trouble', 'gangguan', 'internet mati'],
'tagihan' => ['tagihan', 'bayar', 'invoice', 'pembayaran', 'rekening'],
'pemasangan' => ['pasang', 'berlangganan', 'pemasangan', 'registrasi', 'daftar', 'masang'],
'umum' => []
];
$message = strtolower($message);
foreach ($rules as $topik => $keywords) {
foreach ($keywords as $keyword) {
if (strpos($message, $keyword) !== false) {
return $topik;
}
}
}
return 'umum';
}
private function get_data_pelanggan($sender) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT nama, nama_paket, status, akun, status_koneksi FROM v_pelanggan WHERE nomor_whatsapp = ?");
$stmt->execute([$sender]);
$pelanggan = $stmt->fetch(PDO::FETCH_ASSOC);
if ($pelanggan) {
return "Pelanggan atas nama {$pelanggan['nama']} (ID: {$pelanggan['akun']}) menggunakan paket {$pelanggan['nama_paket']} dan status koneksi {$pelanggan['status_koneksi']}. Status langganan: {$pelanggan['status']}.";
}
return "Data pelanggan tidak ditemukan.";
}
private function get_id_server($apikey_wa) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT id FROM data_server WHERE apikey_whatsapp = ?");
$stmt->execute([$apikey_wa]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row['id'] ?? null;
}
private function get_paket_berlangganan($id) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT nama, harga_paket FROM profile_paket WHERE id_data_server = ?");
$stmt->execute([$id]);
$pakets = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$pakets) return "Paket berlangganan belum tersedia.";
$list = "Berikut adalah daftar paket layanan:\n";
foreach ($pakets as $p) {
$list .= "- {$p['nama']}: Rp " . number_format($p['harga_paket'], 0, ',', '.') . "\n";
}
return trim($list);
}
private function haversine($lat1, $lon1, $lat2, $lon2) {
$earthRadius = 6371;
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) ** 2;
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return $earthRadius * $c;
}
private function get_info_pemasangan($id, $lat = null, $lon = null) {
$pdo = $this->connectDB();
$stmt1 = $pdo->prepare("SELECT harga_sewa FROM data_server WHERE id = ?");
$stmt1->execute([$id]);
$row = $stmt1->fetch(PDO::FETCH_ASSOC);
$biaya = isset($row['harga_sewa']) ? "Biaya pemasangan sebesar Rp " . number_format($row['harga_sewa'], 0, ',', '.') . "." : "";
$stmt2 = $pdo->prepare("SELECT nama FROM master_desa WHERE id_data_server = ?");
$stmt2->execute([$id]);
$desas = $stmt2->fetchAll(PDO::FETCH_ASSOC);
$wilayah = $desas ? implode(", ", array_column($desas, 'nama')) : "Wilayah layanan belum terdaftar.";
$stmt3 = $pdo->prepare("SELECT titik_koordinat FROM master_topologi WHERE id_data_server = ?");
$stmt3->execute([$id]);
$koordinats = $stmt3->fetchAll(PDO::FETCH_ASSOC);
if ($lat !== null && $lon !== null && $koordinats) {
foreach ($koordinats as $k) {
$raw = $k['titik_koordinat'];
// Cek apakah tidak kosong dan mengandung koma
if (!empty($raw) && strpos($raw, ',') !== false) {
$parts = explode(',', $raw);
// Pastikan hasil explode memiliki dua elemen
if (count($parts) === 2) {
$latParsed = trim($parts[0]);
$lonParsed = trim($parts[1]);
// Validasi bahwa lat dan lon adalah angka
if (is_numeric($latParsed) && is_numeric($lonParsed)) {
$distance = $this->haversine($lat, $lon, (float)$latParsed, (float)$lonParsed);
if ($distance <= 0.3) {
$formulir = "Anda berada dalam jangkauan layanan kami.\n\nSilakan lengkapi data berikut untuk registrasi:\n"
. "- Nama lengkap\n"
. "- NIK\n"
. "- Alamat Lengkap\n"
. "- Titik Koordinat\n"
. "- Nomor WhatsApp\n"
. "- Paket Layanan";
return "$biaya\n$formulir";
}
}
}
}
}
return "Mohon maaf, titik koordinat Anda belum terjangkau jaringan kami.";
}
return "$biaya Wilayah layanan mencakup: $wilayah.\n\nSilakan kirimkan titik koordinat lokasi Anda agar kami bisa melakukan pengecekan jangkauan.";
}
private function get_data_tagihan($sender) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT nama, total_tagihan, limit_pembayaran, tanggal_bayar, status FROM v_tagihan WHERE nomor_whatsapp = ? ORDER BY tanggal_bayar DESC");
$stmt->execute([$sender]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$rows) return "Tidak ada tagihan yang ditemukan.";
$list = "Riwayat tagihan:\n";
foreach ($rows as $row) {
$list .= "- Tagihan: Rp " . number_format($row['total_tagihan'], 0, ',', '.') . " | Jatuh Tempo: {$row['limit_pembayaran']} | Status: {$row['status']}\n";
}
return trim($list);
}
private function simpan_sesi($sender, $topik) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("INSERT INTO chat_session (nomor_whatsapp, topik_terakhir) VALUES (?, ?) ON DUPLICATE KEY UPDATE topik_terakhir = VALUES(topik_terakhir)");
$stmt->execute([$sender, $topik]);
}
private function ambil_sesi($sender) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT topik_terakhir FROM chat_session WHERE nomor_whatsapp = ?");
$stmt->execute([$sender]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row['topik_terakhir'] ?? null;
}
private function simpan_log($sender, $role, $pesan, $id) {
$pdo = $this->connectDB();
$stmt = $pdo->prepare("INSERT INTO chat_log (nomor_whatsapp, role, pesan, id_data_server) VALUES (?, ?, ?, ?)");
$stmt->execute([$sender, $role, $pesan, $id]);
}
public function respon_chat($message, $sender, $apikey_wa) {
$OPENAI_API_KEY = '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'; // Ganti dengan API key valid
$id = $this->get_id_server($apikey_wa);
if (empty($message) || empty($sender) || !$id) {
return [
'id' => $id,
'nomor' => $sender,
'respon' => 'Input tidak valid atau data server tidak ditemukan.'
];
}
$topik = $this->deteksi_topik($message);
$topik_sesi = $this->ambil_sesi($sender);
if (!$topik && $topik_sesi) {
$topik = $topik_sesi;
}
if ($topik) {
$this->simpan_sesi($sender, $topik);
}
$this->simpan_log($sender, 'user', $message, $id);
$pdo = $this->connectDB();
$stmt = $pdo->prepare("SELECT role, pesan FROM chat_log WHERE id_data_server = ? AND nomor_whatsapp = ? ORDER BY id DESC LIMIT 10");
$stmt->execute([$id, $sender]);
$riwayat = array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC));
$messages = [];
foreach ($riwayat as $log) {
$messages[] = ['role' => $log['role'], 'content' => $log['pesan']];
}
$messages[] = ['role' => 'user', 'content' => $message];
$data_pelanggan = $this->get_data_pelanggan($sender);
$info_paket = $this->get_paket_berlangganan($id);
$info_pasang = $this->get_info_pemasangan($id);
$data_tagihan = $this->get_data_tagihan($sender);
$roles = [
'gangguan' => 'Kamu adalah customer service penyedia layanan internet untuk masalah teknis.',
'tagihan' => 'Kamu adalah customer service penyedia layanan internet untuk urusan tagihan.',
'pemasangan' => 'Kamu adalah customer service penyedia layanan internet untuk pemasangan baru.',
'umum' => 'Kamu adalah customer service penyedia layanan internet untuk pertanyaan umum.'
];
$topik_final = $topik ?: 'umum';
switch ($topik_final) {
case 'tagihan':
$tambahan = $data_tagihan;
break;
case 'pemasangan':
$tambahan = $info_paket . "\n" . $info_pasang;
break;
default:
$tambahan = '';
break;
}
$system_message = $roles[$topik_final] . " Data pelanggan: $data_pelanggan\n$tambahan\nJawaban maksimal 3 kalimat, singkat, sopan, dan informatif.";
array_unshift($messages, ['role' => 'system', 'content' => $system_message]);
$payload = [
'model' => 'gpt-3.5-turbo',
'messages' => $messages,
'temperature' => 0.7,
'max_tokens' => 100
];
$ch = curl_init('https://api.openai.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $OPENAI_API_KEY,
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return [
'id' => $id,
'nomor' => $sender,
'respon' => 'Terjadi kesalahan koneksi ke OpenAI: ' . $error
];
}
curl_close($ch);
$data = json_decode($response, true);
$reply = $data['choices'][0]['message']['content'] ?? 'Maaf, tidak dapat memberikan jawaban saat ini.';
$this->simpan_log($sender, 'assistant', $reply, $id);
$hasil = [
'id' => $id,
'nomor' => $sender,
'respon' => trim($reply)
];
// Debug log
// file_put_contents("debug_respon_chat.log",
// "====\n[" . date("Y-m-d H:i:s") . "]\n".
// "Payload:\n" . print_r($payload, true) .
// "Response:\n" . print_r($data, true) .
// "Final:\n" . print_r($hasil, true),
// FILE_APPEND
// );
return $hasil;
}
}