Merge remote-tracking branch 'origin/feature/chatbot-sidebar' into dev

This commit is contained in:
arifal
2025-03-04 17:32:37 +07:00
7 changed files with 356 additions and 266 deletions

View File

@@ -88,9 +88,42 @@ class ChatbotController extends Controller
try { try {
// Panggil service untuk generate text // Panggil service untuk generate text
$response = $this->openAIService->mainGenerateText($request->input('prompt')); $classifyResponse = $this->openAIService->classifyMainGenerateText($request->input('prompt'));
info($classifyResponse);
// Pastikan hasil klasifikasi valid sebelum melanjutkan
$validCategories = [
'reklame', 'business_or_industries', 'customers',
'pbg', 'retribusi', 'spatial_plannings',
'tourisms', 'umkms'
];
if (!in_array($classifyResponse, $validCategories)) {
return response()->json([
'error' => ''
], 400);
}
$queryResponse = $this->openAIService->createMainQuery($classifyResponse, $request->input('prompt'));
info($queryResponse);
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$formattedResultQuery = "[]";
return response()->json(['response' => $response]); if($firstValidation === "VALID" && $secondValidation === "VALID")
{
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
$queryResult = DB::select($queryResponse);
info($queryResult);
$formattedResultQuery = json_encode($queryResult, JSON_PRETTY_PRINT);
info($formattedResultQuery);
}
$nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery);
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
return response()->json(['response' => $finalGeneratedText]);
} catch (\Exception $e) { } catch (\Exception $e) {
// Tangani error dan log exception // Tangani error dan log exception
\Log::error("Error generating text: " . $e->getMessage()); \Log::error("Error generating text: " . $e->getMessage());

View File

@@ -153,6 +153,72 @@ class OpenAIService
return trim($response['choices'][0]['message']['content'] ?? 'No response'); return trim($response['choices'][0]['message']['content'] ?? 'No response');
} }
public function classifyMainGenerateText($prompt) {
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an assistant that classifies text into one of the following categories:
- reklame (ads or product/service promotions)
- business_or_industries (business or industries in general)
- customers (customers, consumers, or service users)
- pbg (tasks related to Building Approval)
- retribusi (retributions related to PBG)
- spatial_plannings (spatial planning)
- tourisms (tourism and tourist destinations)
- umkms (Micro, Small, and Medium Enterprises)
Respond with only one of the categories above without any additional explanation."
],
[
'role' => 'user',
'content' => "Classify the following text:\n\n" . $prompt
],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function createMainQuery($classify, $prompt)
{
// Load file JSON
$jsonPath = public_path('templates/table_config.json');
$jsonConfig = json_decode(file_get_contents($jsonPath), true);
// Pastikan kategori tersedia dalam konfigurasi
if (!isset($jsonConfig[$classify])) {
return "Error: Kategori tidak ditemukan dalam konfigurasi.";
}
// Ambil nama tabel dan kolom
$tableName = $jsonConfig[$classify]['table_name'];
$columns = implode(', ', $jsonConfig[$classify]['list_column']);
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an AI assistant that generates only valid MariaDB queries based on user requests.
Use the following table information to construct the SQL query:
- Table Name: $tableName
- Available Columns: $columns
Generate only the SQL query without any explanation or additional text."
],
[
'role' => 'user',
'content' => $prompt
],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function mainGenerateText($prompt) { public function mainGenerateText($prompt) {
$response = $this->client->chat()->create([ $response = $this->client->chat()->create([

View File

@@ -1,145 +1,169 @@
{ {
"v_advertisements": [ "reklame": {
"no", "table_name": "v_advertisements",
"business_name", "list_column": [
"npwpd", "no",
"advertisement_type", "business_name",
"advertisement_content", "npwpd",
"business_address", "advertisement_type",
"advertisement_location", "advertisement_content",
"village_name", "business_address",
"district_name", "advertisement_location",
"length", "village_name",
"width", "district_name",
"viewing_angle", "length",
"face", "width",
"area", "viewing_angle",
"angle", "face",
"contact" "area",
], "angle",
"business_or_industries": [ "contact"
"nama_kecamatan", ]
"nama_kelurahan", },
"nop", "business_or_industries": {
"nama_wajib_pajak", "table_name": "business_or_industries",
"alamat_wajib_pajak", "list_column": [
"alamat_objek_pajak", "nama_kecamatan",
"luas_bumi", "nama_kelurahan",
"luas_bangunan", "nop",
"njop_bumi", "nama_wajib_pajak",
"njop_bangunan", "alamat_wajib_pajak",
"ketetapan", "alamat_objek_pajak",
"tahun_pajak", "luas_bumi",
"created_at", "luas_bangunan",
"updated_at" "njop_bumi",
], "njop_bangunan",
"customers": [ "ketetapan",
"nomor_pelanggan", "tahun_pajak",
"kota_pelayanan", "created_at",
"nama", "updated_at"
"alamat", ]
"latitude", },
"longitude", "customers": {
"created_at", "table_name": "customers",
"updated_at" "list_column": [
], "nomor_pelanggan",
"pbg_task": [ "kota_pelayanan",
"uuid", "nama",
"name", "alamat",
"owner_name", "latitude",
"application_type", "longitude",
"application_type_name", "created_at",
"condition", "updated_at"
"registration_number", ]
"document_number", },
"address", "pbg": {
"status_name", "table_name": "pbg_task",
"slf_status_name", "list_column": [
"function_type", "uuid",
"consultation_type", "name",
"due_date", "owner_name",
"land_certificate_phase", "application_type",
"created_at", "application_type_name",
"updated_at", "condition",
"task_created_at" "registration_number",
], "document_number",
"v_pbg_task_retributions": [ "address",
"uuid", "status_name",
"name", "slf_status_name",
"owner_name", "function_type",
"application_type", "consultation_type",
"application_type_name", "due_date",
"condition", "land_certificate_phase",
"registration_number", "created_at",
"document_number", "updated_at",
"address", "task_created_at"
"status_name", ]
"slf_status_name", },
"consultation_type", "retribusi": {
"due_date", "table_name": "v_pbg_task_with_retributions",
"land_certificate_phase", "list_column": [
"created_at", "uuid",
"updated_at", "name",
"task_created_at", "owner_name",
"nilai_retribusi_bangunan" "application_type",
], "application_type_name",
"spatial_plannings": [ "condition",
"created_at", "registration_number",
"updated_at", "document_number",
"name", "address",
"kbli", "status_name",
"activities", "slf_status_name",
"area", "consultation_type",
"location", "due_date",
"number", "land_certificate_phase",
"date" "created_at",
], "updated_at",
"v_tourisms": [ "task_created_at",
"project_id", "nilai_retribusi_bangunan"
"project_type_id", ]
"nib", },
"business_name", "spatial_plannings": {
"oss_publication_date", "table_name": "spatial_plannings",
"investment_status_description", "list_column": [
"business_form", "created_at",
"project_risk", "updated_at",
"project_name", "name",
"business_scale", "kbli",
"business_address", "activities",
"village_name", "area",
"district_name", "location",
"longitude", "number",
"latitude", "date"
"project_submission_date", ]
"kbli_title", },
"supervisory_sector", "tourisms": {
"user_name", "table_name": "v_tourisms",
"email", "list_column": [
"contact", "project_id",
"land_area_in_m2", "project_type_id",
"investment_amount", "nib",
"tki" "business_name",
], "oss_publication_date",
"v_umkms": [ "investment_status_description",
"business_address", "business_form",
"business_contact", "project_risk",
"business_desc", "project_name",
"business_form", "business_scale",
"business_id_number", "business_address",
"", "village_name",
"", "district_name",
"", "longitude",
"", "latitude",
"", "project_submission_date",
"", "kbli_title",
"", "supervisory_sector",
"", "user_name",
"", "email",
"", "contact",
"", "land_area_in_m2",
"", "investment_amount",
"", "tki"
"", ]
"" },
] "umkms": {
"table_name": "v_umkms",
"list_column": [
"business_address",
"business_contact",
"business_desc",
"business_form",
"business_id_number",
"business_name",
"business_scale",
"business_type",
"created_at",
"district_name",
"land_area",
"number_of_employee",
"owner_address",
"owner_contact",
"owner_id",
"owner_name",
"permit_status",
"revenue",
"updated_at",
"village_name"
]
}
} }

View File

@@ -1,6 +1,12 @@
import GlobalConfig from "../global-config.js"; import GlobalConfig from "../global-config.js";
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const timeElements = document.querySelectorAll(".sending-message-time p");
timeElements.forEach((element) => {
element.textContent = getCurrentTime();
});
const textarea = document.getElementById("user-message"); const textarea = document.getElementById("user-message");
const sendButton = document.getElementById("send"); const sendButton = document.getElementById("send");
const conversationArea = document.querySelector(".row.flex-grow"); const conversationArea = document.querySelector(".row.flex-grow");
@@ -16,13 +22,20 @@ document.addEventListener("DOMContentLoaded", function () {
addMessage(userText, "user"); addMessage(userText, "user");
// Tambahkan pesan bot sementara dengan "Loading..." // Tambahkan pesan bot sementara dengan "Loading..."
const botMessageElement = addMessage('<div class="loader w-auto"></div>', "bot"); const botMessageElement = addMessage('<div class="bot-message-text">...</div>', "bot");
// Panggil API untuk mendapatkan response dari bot const messageTextContainer = botMessageElement.querySelector(".bot-message-text");
if (messageTextContainer) {
messageTextContainer.innerHTML = '<div class="loader ms-3"></div>';
}
// Panggil API untuk mendapatkan respons dari bot
const botResponse = await getBotResponse(userText); const botResponse = await getBotResponse(userText);
// Perbarui pesan bot dengan respons yang sebenarnya // Perbarui pesan bot dengan respons yang sebenarnya
botMessageElement.innerHTML = botResponse;; if (messageTextContainer) {
messageTextContainer.innerHTML = botResponse;
}
} }
} }
@@ -37,54 +50,78 @@ document.addEventListener("DOMContentLoaded", function () {
} }
}); });
function getCurrentTime() {
const now = new Date();
return now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
}
function addMessage(text, sender) { function addMessage(text, sender) {
const messageRow = document.createElement("div"); const messageRow = document.createElement("div");
messageRow.classList.add("row", "flex-grow", "overflow-auto"); // Atur posisi berdasarkan sender (user -> end, bot -> start)
messageRow.classList.add("row", "flex-grow", "overflow-auto", sender === "user" ? "justify-content-end" : "justify-content-start");
const messageCol = document.createElement("div"); const messageCol = document.createElement("div");
messageCol.classList.add("w-auto", "d-inline-block"); // Menyesuaikan lebar konten messageCol.classList.add("col-9", "w-auto");
// Atur lebar maksimum berdasarkan sender
messageCol.style.maxWidth = sender === "user" ? "50%" : "75%";
// Container untuk menyimpan nama dan bubble chat
const messageWrapper = document.createElement("div");
messageWrapper.classList.add("d-flex", "flex-column");
// Tambahkan Nama di luar bubble chat
const messageName = document.createElement("p");
messageName.classList.add("fw-bolder", sender === "user" ? "text-end" : "text-start", "mb-1");
messageName.textContent = sender === "user" ? "You" : "Neng Bedas";
// Bubble Chat
const messageContainer = document.createElement("div");
messageContainer.classList.add("p-2", "rounded", "mb-2", "d-inline-block");
if (sender === "user") { if (sender === "user") {
messageCol.classList.add("ms-auto", "max-w-50"); // Rata kanan, max 50% (setara col-6) messageContainer.classList.add("user-response", "bg-primary", "text-white");
} else { } else {
messageCol.classList.add("max-w-75"); // Max 75% (setara col-9) messageContainer.classList.add("bot-response", "bg-light");
}
// Tambahkan avatar hanya untuk bot const messageContent = document.createElement("div");
const avatarSpan = document.createElement("span"); messageContent.classList.add("bot-message-text", "mb-0", "text-start");
avatarSpan.classList.add("d-flex", "align-items-center", "mb-1"); messageContent.textContent = text;
// Waktu di dalam bubble chat
const messageTime = document.createElement("div");
messageTime.classList.add("sending-message-time", "text-end", "mt-1");
messageTime.innerHTML = `<p class="small mb-0 ${sender === "user" ? "text-white text-start" : "text-muted"}">${getCurrentTime()}</p>`;
messageContainer.appendChild(messageContent);
messageContainer.appendChild(messageTime);
// Jika pengirim adalah bot, tambahkan avatar
if (sender !== "user") {
const avatarContainer = document.createElement("div");
avatarContainer.classList.add("col-auto", "pe-0");
const avatarImg = document.createElement("img"); const avatarImg = document.createElement("img");
avatarImg.classList.add("rounded-circle"); avatarImg.classList.add("rounded-circle");
avatarImg.width = 32; avatarImg.width = 45;
avatarImg.src = "/images/iconchatbot.jpeg"; avatarImg.src = "/images/iconchatbot.jpeg";
avatarImg.alt = "bot-avatar"; avatarImg.alt = "bot-avatar";
avatarSpan.appendChild(avatarImg); avatarContainer.appendChild(avatarImg);
messageCol.appendChild(avatarSpan); messageRow.appendChild(avatarContainer);
} }
const messageDiv = document.createElement("div"); // Masukkan nama dan bubble ke dalam wrapper
messageDiv.classList.add("p-2", "rounded", "mb-2"); messageWrapper.appendChild(messageName);
messageWrapper.appendChild(messageContainer);
if (sender === "user") { messageCol.appendChild(messageWrapper);
messageDiv.classList.add("user-response", "bg-primary", "text-white");
} else {
messageDiv.classList.add("bot-response", "bg-light");
}
// Menyisipkan konten HTML langsung (bisa berupa teks atau loader)
messageDiv.innerHTML = text;
messageCol.appendChild(messageDiv);
messageRow.appendChild(messageCol); messageRow.appendChild(messageCol);
// Tambahkan ke area percakapan
conversationArea.appendChild(messageRow); conversationArea.appendChild(messageRow);
// Scroll otomatis ke bawah
conversationArea.scrollTop = conversationArea.scrollHeight; conversationArea.scrollTop = conversationArea.scrollHeight;
return messageDiv; // Mengembalikan elemen agar bisa diperbarui nanti return messageContainer;
} }
// Fungsi untuk memanggil API // Fungsi untuk memanggil API
async function getBotResponse(userText) { async function getBotResponse(userText) {

View File

@@ -86,64 +86,6 @@ document.addEventListener("DOMContentLoaded", function () {
const now = new Date(); const now = new Date();
return now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0"); return now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
} }
// function addMessage(text, sender) {
// const messageRow = document.createElement("div");
// // Atur posisi berdasarkan sender (user -> end, bot -> start)
// messageRow.classList.add("row", "flex-grow", "overflow-auto", sender === "user" ? "justify-content-end" : "justify-content-start");
// const messageCol = document.createElement("div");
// messageCol.classList.add("col-9", "w-auto");
// // Atur lebar maksimum berdasarkan sender
// messageCol.style.maxWidth = sender === "user" ? "50%" : "75%";
// const messageContainer = document.createElement("div");
// messageContainer.classList.add("p-2", "rounded", "mb-2", "d-flex", "align-items-center");
// if (sender === "user") {
// messageContainer.classList.add("user-response", "bg-primary", "text-white", "ms-auto");
// } else {
// messageContainer.classList.add("bot-response", "bg-light");
// }
// if (sender !== "user") {
// const avatarSpan = document.createElement("span");
// avatarSpan.classList.add("d-flex", "align-self-start", "mb-1");
// const avatarImg = document.createElement("img");
// avatarImg.classList.add("rounded-circle");
// avatarImg.width = 45;
// avatarImg.src = "/images/iconchatbot.jpeg";
// avatarImg.alt = "bot-avatar";
// avatarSpan.appendChild(avatarImg);
// messageContainer.appendChild(avatarSpan);
// }
// const messageContentWrapper = document.createElement("div");
// messageContentWrapper.classList.add("d-flex", "flex-column", "ms-2");
// const messageContent = document.createElement("div");
// messageContent.classList.add("row");
// messageContent.innerHTML = `<p class="fw-bolder ${sender === "user" ? "text-start" : "text-start"} mb-1">${sender === "user" ? "You" : "Neng Bedas"}</p><div class="bot-message-text mb-0 text-start">${text}</div>`;
// const messageTime = document.createElement("div");
// messageTime.classList.add("sending-message-time");
// messageTime.innerHTML = `<p class="small mb-0 ${sender === "user" ? "text-end text-white" : "text-end text-muted"}">${getCurrentTime()}</p>`;
// messageContentWrapper.appendChild(messageContent);
// messageContentWrapper.appendChild(messageTime);
// messageContainer.appendChild(messageContentWrapper);
// messageCol.appendChild(messageContainer);
// messageRow.appendChild(messageCol);
// conversationArea.appendChild(messageRow);
// conversationArea.scrollTop = conversationArea.scrollHeight;
// return messageContainer;
// }
function addMessage(text, sender) { function addMessage(text, sender) {
const messageRow = document.createElement("div"); const messageRow = document.createElement("div");

View File

@@ -32,33 +32,41 @@
<div class="card"> <div class="card">
<div class="card-body d-flex flex-column" style="height: 700px;"> <div class="card-body d-flex flex-column" style="height: 700px;">
<!-- Conversation Area --> <!-- Conversation Area -->
<!-- Bot Response --> <!-- Bot Response -->
<div class="row flex-grow overflow-auto"> <div class="row flex-grow overflow-auto align-items-start">
<!-- Avatar -->
<div class="col-auto alignpe-0">
<img class="rounded-circle" width="45" src="/images/iconchatbot.jpeg" alt="avatar-3">
</div>
<!-- Nama dan Bubble Chat -->
<div class="col-9 w-auto"> <div class="col-9 w-auto">
<span class="d-flex align-items-center mb-1"> <!-- Nama Bot -->
<img class="rounded-circle" width="32" src="/images/iconchatbot.jpeg" alt="avatar-3"> <p class="fw-bolder mb-1">Neng Bedas</p>
</span>
<div class="bot-response p-2 bg-light rounded mb-2"> <!-- Bubble Chat -->
<p>Halo! Ada yang bisa saya bantu?</p> <div class="bot-response p-2 bg-light rounded mb-2 d-inline-block">
<p class="mb-0">Halo! Ada yang bisa saya bantu?</p>
<!-- Waktu (Tetap di Dalam Bubble Chat) -->
<div class="sending-message-time text-end mt-1">
<p class="text-muted small mb-0">Now</p>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Input & Button (Selalu di Bawah) --> <!-- Input & Button (Selalu di Bawah) -->
<div class="row mt-auto"> <div class="row mt-auto">
<div class="col-xl-12 d-flex align-items-end gap-1"> <div class="col-xl-12 d-flex align-items-end gap-1">
<textarea class="form-control" id="user-message"></textarea> <textarea class="form-control" id="user-message"></textarea>
<button id="send" class="btn btn-primary btn-lg rounded-pill"> <button id="send" class="btn btn-primary btn-lg h-100 d-flex align-items-center">
<i class='bx bx-send'></i> <i class='bx bx-send'></i>
</button> </button>
</div> </div>
{{-- <div class="col-xl-2 d-flex justify-content-end">
</div> --}}
</div> </div>
</div>
</div> </div>
</div> </div>

View File

@@ -55,26 +55,6 @@
<div class="card-body d-flex flex-column" style="height: 700px;"> <div class="card-body d-flex flex-column" style="height: 700px;">
<!-- Conversation Area --> <!-- Conversation Area -->
{{-- <!-- Bot Response -->
<div class="row flex-grow overflow-auto">
<div class="col-9 w-auto">
<div class="bot-response p-2 bg-light rounded mb-2 d-flex align-items-center">
<span class="d-flex align-items-center mb-1">
<img class="rounded-circle" width="45" src="/images/iconchatbot.jpeg" alt="avatar-3">
</span>
<div class="d-flex flex-column ms-2">
<div class="row">
<p class="fw-bolder mb-1">Neng Bedas</p>
<p class="mb-0">Halo! Ada yang bisa saya bantu?</p>
</div>
<div class="sending-message-time">
<p class="text-muted small mb-0 text-end">Now</p>
</div>
</div>
</div>
</div>
</div> --}}
<!-- Bot Response --> <!-- Bot Response -->
<div class="row flex-grow overflow-auto align-items-start"> <div class="row flex-grow overflow-auto align-items-start">
<!-- Avatar --> <!-- Avatar -->