GeoNetAgent, LDAPWeb, server-audit, server-connection
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
9.8 KiB

(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 = '<div class="ps-chat__msg ' + (isUser ? 'ps-chat__msg--user' : 'ps-chat__msg--assistant') + '" data-message-id="' + (message.id || '') + '">';
html += '<div class="ps-chat__msg-label">' + (isUser ? 'Anda' : 'Ollama') + '</div>';
html += '<div class="ps-chat__bubble">';
html += '<div class="ps-chat__text whitespace-pre-wrap">' + escapeHtml(message.content || '') + '</div>';
if (!isUser && message.results && message.results.length) {
html += '<details class="ps-chat__results mt-3" open>';
html += '<summary class="text-xs text-cyan-400 cursor-pointer">' + message.results.length + ' folder ditemukan</summary>';
html += '<ul class="mt-2 space-y-1 max-h-40 overflow-y-auto">';
message.results.forEach(function (row) {
html += '<li class="text-xs font-mono text-slate-400 break-all">' + escapeHtml(row.path || '') + '</li>';
});
html += '</ul></details>';
}
html += '</div></div>';
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 = '<div class="ps-chat__empty text-center text-slate-500 text-sm py-12" data-chat-empty>'
+ '<p class="mb-1">Chat diakhiri.</p>'
+ '<p>Mulai percakapan baru dengan pertanyaan di bawah.</p></div>';
}
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);
}
});
})();