diff --git a/app/Http/Controllers/Api/ChatbotController.php b/app/Http/Controllers/Api/ChatbotController.php index 24637cc..a804f79 100644 --- a/app/Http/Controllers/Api/ChatbotController.php +++ b/app/Http/Controllers/Api/ChatbotController.php @@ -88,9 +88,42 @@ class ChatbotController extends Controller try { // 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) { // Tangani error dan log exception \Log::error("Error generating text: " . $e->getMessage()); diff --git a/app/Services/OpenAIService.php b/app/Services/OpenAIService.php index 0e744fd..ef8bbc3 100644 --- a/app/Services/OpenAIService.php +++ b/app/Services/OpenAIService.php @@ -153,6 +153,72 @@ class OpenAIService 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) { $response = $this->client->chat()->create([ diff --git a/public/templates/table_config.json b/public/templates/table_config.json index b8e65f0..dc87f44 100644 --- a/public/templates/table_config.json +++ b/public/templates/table_config.json @@ -1,145 +1,169 @@ { - "v_advertisements": [ - "no", - "business_name", - "npwpd", - "advertisement_type", - "advertisement_content", - "business_address", - "advertisement_location", - "village_name", - "district_name", - "length", - "width", - "viewing_angle", - "face", - "area", - "angle", - "contact" - ], - "business_or_industries": [ - "nama_kecamatan", - "nama_kelurahan", - "nop", - "nama_wajib_pajak", - "alamat_wajib_pajak", - "alamat_objek_pajak", - "luas_bumi", - "luas_bangunan", - "njop_bumi", - "njop_bangunan", - "ketetapan", - "tahun_pajak", - "created_at", - "updated_at" - ], - "customers": [ - "nomor_pelanggan", - "kota_pelayanan", - "nama", - "alamat", - "latitude", - "longitude", - "created_at", - "updated_at" - ], - "pbg_task": [ - "uuid", - "name", - "owner_name", - "application_type", - "application_type_name", - "condition", - "registration_number", - "document_number", - "address", - "status_name", - "slf_status_name", - "function_type", - "consultation_type", - "due_date", - "land_certificate_phase", - "created_at", - "updated_at", - "task_created_at" - ], - "v_pbg_task_retributions": [ - "uuid", - "name", - "owner_name", - "application_type", - "application_type_name", - "condition", - "registration_number", - "document_number", - "address", - "status_name", - "slf_status_name", - "consultation_type", - "due_date", - "land_certificate_phase", - "created_at", - "updated_at", - "task_created_at", - "nilai_retribusi_bangunan" - ], - "spatial_plannings": [ - "created_at", - "updated_at", - "name", - "kbli", - "activities", - "area", - "location", - "number", - "date" - ], - "v_tourisms": [ - "project_id", - "project_type_id", - "nib", - "business_name", - "oss_publication_date", - "investment_status_description", - "business_form", - "project_risk", - "project_name", - "business_scale", - "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" - ], - "v_umkms": [ - "business_address", - "business_contact", - "business_desc", - "business_form", - "business_id_number", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ] + "reklame": { + "table_name": "v_advertisements", + "list_column": [ + "no", + "business_name", + "npwpd", + "advertisement_type", + "advertisement_content", + "business_address", + "advertisement_location", + "village_name", + "district_name", + "length", + "width", + "viewing_angle", + "face", + "area", + "angle", + "contact" + ] + }, + "business_or_industries": { + "table_name": "business_or_industries", + "list_column": [ + "nama_kecamatan", + "nama_kelurahan", + "nop", + "nama_wajib_pajak", + "alamat_wajib_pajak", + "alamat_objek_pajak", + "luas_bumi", + "luas_bangunan", + "njop_bumi", + "njop_bangunan", + "ketetapan", + "tahun_pajak", + "created_at", + "updated_at" + ] + }, + "customers": { + "table_name": "customers", + "list_column": [ + "nomor_pelanggan", + "kota_pelayanan", + "nama", + "alamat", + "latitude", + "longitude", + "created_at", + "updated_at" + ] + }, + "pbg": { + "table_name": "pbg_task", + "list_column": [ + "uuid", + "name", + "owner_name", + "application_type", + "application_type_name", + "condition", + "registration_number", + "document_number", + "address", + "status_name", + "slf_status_name", + "function_type", + "consultation_type", + "due_date", + "land_certificate_phase", + "created_at", + "updated_at", + "task_created_at" + ] + }, + "retribusi": { + "table_name": "v_pbg_task_with_retributions", + "list_column": [ + "uuid", + "name", + "owner_name", + "application_type", + "application_type_name", + "condition", + "registration_number", + "document_number", + "address", + "status_name", + "slf_status_name", + "consultation_type", + "due_date", + "land_certificate_phase", + "created_at", + "updated_at", + "task_created_at", + "nilai_retribusi_bangunan" + ] + }, + "spatial_plannings": { + "table_name": "spatial_plannings", + "list_column": [ + "created_at", + "updated_at", + "name", + "kbli", + "activities", + "area", + "location", + "number", + "date" + ] + }, + "tourisms": { + "table_name": "v_tourisms", + "list_column": [ + "project_id", + "project_type_id", + "nib", + "business_name", + "oss_publication_date", + "investment_status_description", + "business_form", + "project_risk", + "project_name", + "business_scale", + "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" + ] + } } \ No newline at end of file diff --git a/resources/js/chatbot-pimpinan/index.js b/resources/js/chatbot-pimpinan/index.js index c6b311c..dddff96 100644 --- a/resources/js/chatbot-pimpinan/index.js +++ b/resources/js/chatbot-pimpinan/index.js @@ -1,6 +1,12 @@ import GlobalConfig from "../global-config.js"; 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 sendButton = document.getElementById("send"); const conversationArea = document.querySelector(".row.flex-grow"); @@ -16,13 +22,20 @@ document.addEventListener("DOMContentLoaded", function () { addMessage(userText, "user"); // Tambahkan pesan bot sementara dengan "Loading..." - const botMessageElement = addMessage('
', "bot"); + const botMessageElement = addMessage('
...
', "bot"); - // Panggil API untuk mendapatkan response dari bot + const messageTextContainer = botMessageElement.querySelector(".bot-message-text"); + if (messageTextContainer) { + messageTextContainer.innerHTML = '
'; + } + + // Panggil API untuk mendapatkan respons dari bot const botResponse = await getBotResponse(userText); // 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) { 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"); - 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") { - 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 { - messageCol.classList.add("max-w-75"); // Max 75% (setara col-9) + messageContainer.classList.add("bot-response", "bg-light"); + } - // Tambahkan avatar hanya untuk bot - const avatarSpan = document.createElement("span"); - avatarSpan.classList.add("d-flex", "align-items-center", "mb-1"); + const messageContent = document.createElement("div"); + messageContent.classList.add("bot-message-text", "mb-0", "text-start"); + 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 = `

${getCurrentTime()}

`; + + 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"); avatarImg.classList.add("rounded-circle"); - avatarImg.width = 32; + avatarImg.width = 45; avatarImg.src = "/images/iconchatbot.jpeg"; avatarImg.alt = "bot-avatar"; - avatarSpan.appendChild(avatarImg); - messageCol.appendChild(avatarSpan); + avatarContainer.appendChild(avatarImg); + messageRow.appendChild(avatarContainer); } - const messageDiv = document.createElement("div"); - messageDiv.classList.add("p-2", "rounded", "mb-2"); - - if (sender === "user") { - 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); + // Masukkan nama dan bubble ke dalam wrapper + messageWrapper.appendChild(messageName); + messageWrapper.appendChild(messageContainer); + messageCol.appendChild(messageWrapper); messageRow.appendChild(messageCol); - // Tambahkan ke area percakapan conversationArea.appendChild(messageRow); - - // Scroll otomatis ke bawah conversationArea.scrollTop = conversationArea.scrollHeight; - return messageDiv; // Mengembalikan elemen agar bisa diperbarui nanti - } + return messageContainer; + } // Fungsi untuk memanggil API async function getBotResponse(userText) { diff --git a/resources/js/chatbot/index.js b/resources/js/chatbot/index.js index 01076cf..8e157d6 100644 --- a/resources/js/chatbot/index.js +++ b/resources/js/chatbot/index.js @@ -86,64 +86,6 @@ document.addEventListener("DOMContentLoaded", function () { const now = new Date(); 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 = `

${sender === "user" ? "You" : "Neng Bedas"}

${text}
`; - - // const messageTime = document.createElement("div"); - // messageTime.classList.add("sending-message-time"); - // messageTime.innerHTML = `

${getCurrentTime()}

`; - - - // 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) { const messageRow = document.createElement("div"); diff --git a/resources/views/chatbot-pimpinan/index.blade.php b/resources/views/chatbot-pimpinan/index.blade.php index 7ee646f..4ccd5b2 100644 --- a/resources/views/chatbot-pimpinan/index.blade.php +++ b/resources/views/chatbot-pimpinan/index.blade.php @@ -32,33 +32,41 @@
- + -
+
+ +
+ avatar-3 +
+ +
- - avatar-3 - -
-

Halo! Ada yang bisa saya bantu?

+ +

Neng Bedas

+ + +
+

Halo! Ada yang bisa saya bantu?

+ + +
+

Now

+
+
-
- {{--
- -
--}}
-
-
diff --git a/resources/views/chatbot/index.blade.php b/resources/views/chatbot/index.blade.php index 5c9a4a5..c46a843 100644 --- a/resources/views/chatbot/index.blade.php +++ b/resources/views/chatbot/index.blade.php @@ -55,26 +55,6 @@
- {{-- -
-
-
- - avatar-3 - -
-
-

Neng Bedas

-

Halo! Ada yang bisa saya bantu?

-
-
-

Now

-
-
-
-
-
--}} -