(function () { 'use strict'; function init(root) { var app = root || document.getElementById('project-search-app'); if (!app || app.dataset.psChatBound === '1') { return; } app.dataset.psChatBound = '1'; var form = app.querySelector('#project-search-form'); var input = app.querySelector('#project-search-query'); var messagesEl = app.querySelector('#project-search-messages'); var statusEl = app.querySelector('#project-search-status'); var errorEl = app.querySelector('#project-search-error'); var submitBtn = app.querySelector('#project-search-submit'); var endBtn = app.querySelector('#project-search-end-chat'); var askUrl = app.dataset.askUrl; var endUrl = app.dataset.endUrl; var csrf = document.querySelector('meta[name="csrf-token"]'); var pending = false; function csrfToken() { return csrf ? csrf.getAttribute('content') : ''; } function hideError() { if (!errorEl) return; errorEl.classList.add('hidden'); errorEl.textContent = ''; } function showError(message) { if (!errorEl) return; errorEl.textContent = message; errorEl.classList.remove('hidden'); } function setPending(active) { pending = active; if (statusEl) { statusEl.classList.toggle('hidden', !active); } if (submitBtn) { submitBtn.disabled = active || app.dataset.indexAvailable !== '1' || app.dataset.chatAvailable !== '1'; } if (input) { input.disabled = active || app.dataset.indexAvailable !== '1' || app.dataset.chatAvailable !== '1'; } } function removeEmptyState() { var empty = messagesEl ? messagesEl.querySelector('[data-chat-empty]') : null; if (empty) { empty.remove(); } } function escapeHtml(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function renderMessage(message) { var isUser = message.role === 'user'; var html = '
'; html += '
' + (isUser ? 'Anda' : 'Ollama') + '
'; html += '
'; html += '
' + escapeHtml(message.content || '') + '
'; if (!isUser && message.results && message.results.length) { html += '
'; html += '' + message.results.length + ' folder ditemukan'; html += '
    '; message.results.forEach(function (row) { html += '
  • ' + escapeHtml(row.path || '') + '
  • '; }); html += '
'; } html += '
'; return html; } function appendMessage(message) { if (!messagesEl) return; removeEmptyState(); messagesEl.insertAdjacentHTML('beforeend', renderMessage(message)); messagesEl.scrollTop = messagesEl.scrollHeight; } function ensureEndChatButton() { if (endBtn || !app) return; var header = app.querySelector('.mt-6.mb-6'); if (!header) return; var btn = document.createElement('button'); btn.type = 'button'; btn.id = 'project-search-end-chat'; btn.className = 'rounded-lg border border-slate-600 hover:border-rose-500 hover:text-rose-300 px-4 py-2 text-sm text-slate-300'; btn.textContent = 'End Chat'; header.appendChild(btn); btn.addEventListener('click', endChat); } function getSources() { if (!form) return ['project']; var checked = form.querySelectorAll('input[name="sources[]"]:checked'); return Array.prototype.map.call(checked, function (el) { return el.value; }); } function parseResponse(res) { var contentType = res.headers.get('content-type') || ''; return res.text().then(function (text) { if (contentType.indexOf('application/json') !== -1) { try { return { ok: res.ok, data: JSON.parse(text) }; } catch (e) { throw new Error('Respons server tidak valid.'); } } if (res.status === 504 || res.status === 502) { throw new Error('Waktu permintaan habis. Ollama membutuhkan waktu lebih lama — coba lagi dalam beberapa detik.'); } if (res.status === 419) { throw new Error('Sesi habis. Muat ulang halaman dan login kembali.'); } if (res.status === 401 || res.status === 302) { throw new Error('Sesi login tidak valid. Muat ulang halaman.'); } throw new Error('Server error (HTTP ' + res.status + ').'); }); } function sendMessage(query) { if (pending || !askUrl) return; hideError(); setPending(true); appendMessage({ role: 'user', content: query }); fetch(askUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, body: JSON.stringify({ query: query, sources: getSources(), limit: 10, }), }) .then(function (res) { return parseResponse(res); }) .then(function (result) { if (!result.ok) { throw new Error(result.data.message || 'Permintaan gagal.'); } return result.data; }) .then(function (data) { if (data.assistant_message) { appendMessage(data.assistant_message); } ensureEndChatButton(); }) .catch(function (err) { showError(err.message || 'Gagal memproses pencarian.'); }) .finally(function () { setPending(false); if (input) { input.focus(); } }); } function endChat() { if (!endUrl || !window.confirm('Akhiri chat ini dan mulai sesi baru?')) { return; } hideError(); setPending(true); fetch(endUrl, { method: 'POST', headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, }) .then(function (res) { return parseResponse(res); }) .then(function (result) { if (!result.ok) { throw new Error(result.data.message || 'Gagal mengakhiri chat.'); } return result.data; }) .then(function () { if (messagesEl) { messagesEl.innerHTML = '
' + '

Chat diakhiri.

' + '

Mulai percakapan baru dengan pertanyaan di bawah.

'; } var btn = app.querySelector('#project-search-end-chat'); if (btn) { btn.remove(); } }) .catch(function (err) { showError(err.message || 'Gagal mengakhiri chat.'); }) .finally(function () { setPending(false); }); } if (form) { form.addEventListener('submit', function (event) { event.preventDefault(); if (!input) return; var query = input.value.trim(); if (query.length < 3) return; input.value = ''; sendMessage(query); }); } if (input) { input.addEventListener('keydown', function (event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); form.dispatchEvent(new Event('submit', { cancelable: true })); } }); } if (endBtn) { endBtn.addEventListener('click', endChat); } } document.addEventListener('DOMContentLoaded', function () { init(); }); document.addEventListener('turbo:frame-load', function (event) { if (event.target && event.target.id === 'app-content') { init(event.target); } }); })();