{"id":552,"date":"2025-05-27T01:46:38","date_gmt":"2025-05-27T01:46:38","guid":{"rendered":"https:\/\/abatablaster.xyz\/?page_id=552"},"modified":"2025-07-05T16:53:34","modified_gmt":"2025-07-05T16:53:34","slug":"ai-insight","status":"publish","type":"page","link":"https:\/\/abatablaster.xyz\/index.php\/ai-insight\/","title":{"rendered":"AI-Insight"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"ms\">\n<head>\n<meta charset=\"UTF-8\">\n<title>AI-Insight \u2013 Analisis Mesej AI<\/title>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-app-compat.js\"><\/script>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-firestore-compat.js\"><\/script>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/9.22.2\/firebase-auth-compat.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/sweetalert2@11\"><\/script>\n\n<style>\n  body { font-family: Arial, sans-serif; background: #f1f8f4; padding: 20px; }\n  h2 { color: #2e7d32; }\n  table { width: 100%; border-collapse: collapse; margin-top: 20px; background: white; }\n  th, td { border: 1px solid #ccc; padding: 8px; text-align: left; font-size: 14px; }\n  th { background-color: #e0f2f1; }\n  tr:nth-child(even) { background-color: #f9f9f9; }\n  .controls { margin-bottom: 10px; }\n  h3 { color: #2e7d32; margin-top: 30px; }\n\n<\/style>\n<\/head>\n<body>\n\n<h2>\ud83d\udcca AI-Insight \u2013 Analisis Mesej AI<\/h2>\n\n<div class=\"controls\">\n  <label for=\"numberSelect\">Pilih Nombor:<\/label>\n  <select id=\"numberSelect\" onchange=\"loadLogs()\"><\/select>\n  <button onclick=\"loadLogs()\">\ud83d\udd04 Refresh Data<\/button>\n<\/div>\n\n<table id=\"logTable\">\n  <thead>\n    <tr>\n      <th>\ud83d\udcac Soalan Pelanggan<\/th>\n      <th>\ud83e\udd16 Balasan AI<\/th>\n      <th>\ud83d\udd52 Timestamp<\/th>\n      <th>\u2699\ufe0f Pilih<\/th> <!-- Tambah -->\n    <\/tr>\n  <\/thead>\n  <tbody><\/tbody>\n<\/table>\n\n<h3>\ud83d\udcda Senarai Prompt Tuning<\/h3>\n<table id=\"tuningTable\">\n  <thead>\n    <tr>\n      <th>Soalan Pelanggan<\/th>\n      <th>Balasan AI<\/th>\n      <th>Padam<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody><\/tbody>\n<\/table>\n\n\n<script>\n\/\/ \u2705 Firebase config\n    const firebaseConfig = {\n      apiKey: \"AIzaSyBou8nlJ7uPZ4ioOJapzC8Dn3-K7Qs-yco\",\n      authDomain: \"whatsapp-ai-saas.firebaseapp.com\",\n      projectId: \"whatsapp-ai-saas\",\n      storageBucket: \"whatsapp-ai-saas.appspot.com\",\n      messagingSenderId: \"287462007544\",\n      appId: \"1:287462007544:web:913db884edf906a37cd10d\"\n    };\nfirebase.initializeApp(firebaseConfig);\nconst db = firebase.firestore();\nconst auth = firebase.auth();\n\nlet currentUid = null;\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  \/\/ Semak jika popup telah disembunyikan (localStorage flag)\n  const skipPopup = localStorage.getItem(\"hideAiInsightIntro\");\n  if (!skipPopup) {\n    Swal.fire({\n      title: \"Selamat Datang ke AI-Insight\",\n      html: `\n        <p style=\"text-align:left;font-size:14px;\">\n          AI-Insight membolehkan anda semak log mesej pelanggan dan balasan AI.<br>\n          Anda juga boleh edit & simpan balasan yang lebih tepat supaya AI lebih kenal gaya bisnes anda! \ud83d\ude80\n        <\/p>\n        <div style=\"text-align:left;margin-top:10px;\">\n          <input type=\"checkbox\" id=\"dontShowAgain\">\n          <label for=\"dontShowAgain\" style=\"font-size:13px;\">Jangan tunjukkan lagi<\/label>\n        <\/div>\n      `,\n      icon: \"info\",\n      confirmButtonText: \"OK\",\n      confirmButtonColor: \"#43a047\",\n      allowOutsideClick: false,\n      allowEscapeKey: false,\n      didClose: () => {\n        \/\/ Kalau user tick checkbox, simpan flag ke localStorage\n        const dontShow = document.getElementById(\"dontShowAgain\").checked;\n        if (dontShow) {\n          localStorage.setItem(\"hideAiInsightIntro\", \"true\");\n        }\n      }\n    });\n  }\n});\n\n\n\/\/ \u2705 Load nombor unik\n\/\/ \u2705 Load nombor unik dengan status + nama + emoji\nasync function loadNumbers(currentUid) {\n  const snap = await db.collection(\"user_numbers\").doc(currentUid).get();\n  if (!snap.exists) return;\n\n  let senarai = snap.data().nombor;\n  if (!senarai) return;\n\n  \/\/ Support array of string\/obj\n  if (!Array.isArray(senarai)) {\n    senarai = [senarai];\n  }\n\n  const select = document.getElementById(\"numberSelect\");\n  select.innerHTML = \"\";\n\n  \/\/ --- Ambil status WhatsApp serentak\n  const statusMap = {};\n  await Promise.all(senarai.map(async item => {\n    const phone = typeof item === \"string\" ? item : item.phone;\n    const nama = typeof item === \"string\" ? \"\" : (item.nama || \"\");\n    let status = \"TIDAK_BERSAMBUNG\";\n    try {\n      const qrDoc = await db.collection(\"whatsapp_qr\").doc(phone).get();\n      status = qrDoc.exists ? (qrDoc.data().status || \"TIDAK_BERSAMBUNG\") : \"TIDAK_BERSAMBUNG\";\n    } catch {}\n    statusMap[phone] = { nama, status };\n  }));\n\n  senarai.forEach(item => {\n    const phone = typeof item === \"string\" ? item : item.phone;\n    const nama = typeof item === \"string\" ? \"\" : (item.nama || \"\");\n    const { status } = statusMap[phone] || {};\n    const emoji = status === \"BERSAMBUNG\" ? \"\ud83d\udfe2\" : \"\ud83d\udd34\";\n    const opt = document.createElement(\"option\");\n    opt.value = phone;\n    opt.textContent = `${phone}${nama ? \" \u2013 \" + nama : \"\"} ${emoji}`;\n    select.appendChild(opt);\n  });\n\n  if (select.options.length > 0) {\n    loadLogs();\n  }\n}\n\n\/\/ \u2705 Load logs ikut nombor terpilih\nasync function loadLogs() {\n  const number = document.getElementById(\"numberSelect\").value;\n  const tbody = document.querySelector(\"#logTable tbody\");\n\n    if (!number) {\n    console.log(\"\u274c Tiada nombor dipilih.\");\n    tbody.innerHTML = \"<tr><td colspan='3'>Tiada nombor dipilih.<\/td><\/tr>\";\n    return;\n  }\n\n  tbody.innerHTML = \"<tr><td colspan='3'>Loading...<\/td><\/tr>\";\n\nconst snapshot = await db.collection(\"ai_customer_logs\").doc(number)\n  .collection(\"messages\").orderBy(\"timestamp\", \"desc\").limit(50).get();\n\n  let html = \"\";\n  snapshot.forEach(doc => {\n    const data = doc.data();\nhtml += `\n  <tr>\n    <td>${data.fromUser}<\/td>\n    <td>${data.fromAI}<\/td>\n    <td>${new Date(data.timestamp).toLocaleString()}<\/td>\n    <td><button onclick=\"editBeforeSimpan('${data.fromUser}', \\`${data.fromAI}\\`)\">\u270f\ufe0f Edit & Simpan<\/button><\/td>\n  <\/tr>\n`;\n\n  });\n\n  tbody.innerHTML = html || \"<tr><td colspan='3'>Tiada data log dijumpai.<\/td><\/tr>\";\n}\n\n\/\/ \ud83d\ude80 Mula load nombor\nauth.onAuthStateChanged(user => {\n  if (user) {\n     currentUid = user.uid;\n    loadNumbers(currentUid);\n    loadPromptTuning(); \/\/ <--- Tambah baris ini\n  } else {\n    alert(\"Sila login untuk akses AI Insight.\");\n  }\n});\n\nasync function simpanPromptTuning(soalan, jawapan) {\n  if (!currentUid) {\n    alert(\"\u274c Tiada UID pengguna. Sila login semula.\");\n    return;\n  }\n\nawait db.collection(\"prompt_tuning\")\n  .doc(currentUid)\n  .collection(\"logs\")\n  .add({\n    fromUser: soalan,\n    fromAI: jawapan,\n    timestamp: Date.now()\n  });\n\n  Swal.fire(\"\u2705 Berjaya\", \"Prompt tuning berjaya disimpan!\", \"success\");\n  loadPromptTuning();\n}\n\nasync function editBeforeSimpan(soalan, jawapan) {\n  const { value: newJawapan } = await Swal.fire({\n    title: 'Edit Balasan AI Sebelum Simpan',\n    input: 'textarea',\n    inputLabel: 'Balasan AI:',\n    inputValue: jawapan,\n    showCancelButton: true,\n    confirmButtonText: 'Simpan ke Prompt Tuning',\n    confirmButtonColor: '#43a047'\n  });\n\n  if (newJawapan) {\n    await db.collection(\"prompt_tuning\").doc(currentUid)\n      .collection(\"logs\").add({\n        fromUser: soalan,\n        fromAI: newJawapan,\n        timestamp: Date.now()\n      });\n    Swal.fire(\"\u2705 Berjaya\", \"Prompt tuning berjaya disimpan!\", \"success\");\n    loadPromptTuning();\n  }\n}\n\n\nasync function loadPromptTuning() {\n  const tbody = document.querySelector(\"#tuningTable tbody\");\n  tbody.innerHTML = \"<tr><td colspan='3'>Loading...<\/td><\/tr>\";\n\n  const snapshot = await db.collection(\"prompt_tuning\")\n    .doc(currentUid)\n    .collection(\"logs\")\n    .orderBy(\"timestamp\", \"desc\")\n    .limit(50)\n    .get();\n\n  let html = \"\";\n  snapshot.forEach(doc => {\n    const data = doc.data();\nhtml += `\n  <tr>\n    <td>${data.fromUser}<\/td>\n    <td>${data.fromAI}<\/td>\n    <td>\n      <button onclick=\"padamPromptTuning('${doc.id}')\">\u274c Padam<\/button>\n      <button onclick=\"editPromptTuning('${doc.id}', \\`${data.fromUser}\\`, \\`${data.fromAI}\\`)\">\u270f\ufe0f Edit<\/button>\n    <\/td>\n  <\/tr>\n`;\n\n  });\n\n  tbody.innerHTML = html || \"<tr><td colspan='3'>Tiada log tuning dijumpai.<\/td><\/tr>\";\n}\n\nasync function padamPromptTuning(id) {\n  if (!confirm(\"Padam log tuning ini?\")) return;\n  await db.collection(\"prompt_tuning\")\n    .doc(currentUid)\n    .collection(\"logs\")\n    .doc(id)\n    .delete();\n  loadPromptTuning();\n}\n\nasync function editPromptTuning(id, soalan, jawapan) {\n  const { value: newJawapan } = await Swal.fire({\n    title: 'Edit Balasan AI',\n    input: 'textarea',\n    inputLabel: 'Balasan AI untuk Soalan:\\n' + soalan,\n    inputValue: jawapan,\n    showCancelButton: true,\n    confirmButtonText: 'Simpan',\n    confirmButtonColor: '#43a047'\n  });\n\n  if (newJawapan) {\n    await db.collection(\"prompt_tuning\").doc(currentUid)\n      .collection(\"logs\").doc(id).update({\n        fromAI: newJawapan\n      });\n    Swal.fire(\"\u2705 Berjaya\", \"Balasan AI dikemaskini!\", \"success\");\n    loadPromptTuning();\n  }\n}\n\n<\/script>\n\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>AI-Insight \u2013 Analisis Mesej AI \ud83d\udcca AI-Insight \u2013 Analisis Mesej AI Pilih Nombor: \ud83d\udd04 Refresh Data \ud83d\udcac Soalan Pelanggan \ud83e\udd16 Balasan AI \ud83d\udd52 Timestamp \u2699\ufe0f Pilih \ud83d\udcda Senarai Prompt Tuning Soalan Pelanggan Balasan AI Padam<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-552","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=552"}],"version-history":[{"count":15,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/552\/revisions"}],"predecessor-version":[{"id":732,"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/pages\/552\/revisions\/732"}],"wp:attachment":[{"href":"https:\/\/abatablaster.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}