Update static/js/script.js
Browse files- static/js/script.js +32 -15
static/js/script.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
/**
|
| 2 |
* ORBIT β Educational Research Assistant
|
| 3 |
-
*
|
| 4 |
*/
|
| 5 |
|
| 6 |
// βββββββββββββββββββββββββββββββββββββ
|
|
@@ -13,11 +13,12 @@ const addEvt = (id, event, handler) => {
|
|
| 13 |
};
|
| 14 |
|
| 15 |
// βββββββββββββββββββββββββββββββββββββ
|
| 16 |
-
// 2. State & Data
|
| 17 |
// βββββββββββββββββββββββββββββββββββββ
|
| 18 |
let currentUser = null;
|
| 19 |
let appSettings = null;
|
| 20 |
-
|
|
|
|
| 21 |
let currentSid = null;
|
| 22 |
let pdfText = null;
|
| 23 |
let pdfFilename = null;
|
|
@@ -50,9 +51,14 @@ async function initApp() {
|
|
| 50 |
} catch (error) {
|
| 51 |
console.error("Init Error:", error);
|
| 52 |
} finally {
|
| 53 |
-
// Harus selalu dijalankan meskipun gagal fetch
|
| 54 |
populateModelSelect();
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
const ids = Object.keys(sessions).sort((a, b) => Number(b) - Number(a));
|
| 57 |
if (ids.length > 0) {
|
| 58 |
loadSession(ids[0]);
|
|
@@ -66,7 +72,7 @@ async function initApp() {
|
|
| 66 |
// 4. Session & History Logic
|
| 67 |
// βββββββββββββββββββββββββββββββββββββ
|
| 68 |
function saveSessions() {
|
| 69 |
-
localStorage.setItem('
|
| 70 |
}
|
| 71 |
|
| 72 |
function newSession() {
|
|
@@ -78,6 +84,12 @@ function newSession() {
|
|
| 78 |
function loadSession(id) {
|
| 79 |
if (!sessions[id]) return;
|
| 80 |
currentSid = id;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
if(chatMessages) chatMessages.innerHTML = '';
|
| 82 |
|
| 83 |
const msgs = sessions[id].messages || [];
|
|
@@ -113,13 +125,15 @@ function renderSidebar() {
|
|
| 113 |
return `
|
| 114 |
<button onclick="window.loadSession('${id}')" class="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition-all text-left truncate ${activeClass}">
|
| 115 |
<svg class="w-4 h-4 shrink-0 opacity-60" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
| 116 |
-
<span class="truncate">${sessions[id].title}</span>
|
| 117 |
</button>`;
|
| 118 |
}).join('');
|
| 119 |
}
|
| 120 |
|
| 121 |
function appendMessage(role, content, displayContent) {
|
| 122 |
-
if(!sessions[currentSid])
|
|
|
|
|
|
|
| 123 |
sessions[currentSid].messages.push({ role, content, displayContent });
|
| 124 |
|
| 125 |
if (role === "user" && sessions[currentSid].messages.filter(m => m.role === "user").length === 1) {
|
|
@@ -143,7 +157,6 @@ function scrollBottom() {
|
|
| 143 |
if(chatMessages) chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 144 |
}
|
| 145 |
|
| 146 |
-
// Fallback Markdown jika Marked.js gagal load
|
| 147 |
function simpleMarkdown(text) {
|
| 148 |
let h = String(text || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
| 149 |
h = h.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, c) => `<pre><code>${c.trim()}</code></pre>`);
|
|
@@ -222,6 +235,11 @@ async function sendChat() {
|
|
| 222 |
if(chatTextarea) { chatTextarea.value = ""; chatTextarea.style.height = "auto"; }
|
| 223 |
if($("welcome-msg")) $("welcome-msg").remove();
|
| 224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
setBusy(true);
|
| 226 |
renderBubble("user", displayPrompt);
|
| 227 |
appendMessage("user", fullPrompt, displayPrompt);
|
|
@@ -236,7 +254,7 @@ async function sendChat() {
|
|
| 236 |
const payload = {
|
| 237 |
prompt: fullPrompt,
|
| 238 |
model: $("model-select") ? $("model-select").value : (appSettings?.current_model || ""),
|
| 239 |
-
messages: sessions[currentSid].messages.slice(0, -1)
|
| 240 |
};
|
| 241 |
|
| 242 |
const res = await fetch("/api/chat", {
|
|
@@ -341,8 +359,7 @@ function closeSettings() {
|
|
| 341 |
if($("settings-modal")) $("settings-modal").classList.add("hidden");
|
| 342 |
}
|
| 343 |
|
| 344 |
-
|
| 345 |
-
addEvt("btn-open-settings", "click", openSettings); // Sesuai dengan ID HTML terbaru
|
| 346 |
addEvt("btn-close-settings", "click", closeSettings);
|
| 347 |
addEvt("btn-cancel-settings", "click", closeSettings);
|
| 348 |
|
|
@@ -391,9 +408,9 @@ addEvt("btn-save-settings", "click", async () => {
|
|
| 391 |
appSettings = await res.json();
|
| 392 |
populateModelSelect();
|
| 393 |
closeSettings();
|
| 394 |
-
renderSys("Settings
|
| 395 |
} catch (err) {
|
| 396 |
-
renderSys(`
|
| 397 |
}
|
| 398 |
});
|
| 399 |
|
|
@@ -424,7 +441,7 @@ addEvt("btn-validate-doi-submit", "click", async () => {
|
|
| 424 |
const res = await fetch("/api/validate_doi", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ doi }) });
|
| 425 |
const data = await res.json();
|
| 426 |
if (!res.ok || data.error) {
|
| 427 |
-
resDiv.innerHTML = `<p class="text-red-500 font-medium">Error: ${data.error || "
|
| 428 |
} else {
|
| 429 |
resDiv.innerHTML = `
|
| 430 |
<div class="space-y-2">
|
|
@@ -464,7 +481,7 @@ addEvt("sidebar-overlay", "click", toggleSidebar);
|
|
| 464 |
addEvt("btn-new-chat", "click", newSession);
|
| 465 |
|
| 466 |
function handleClear() {
|
| 467 |
-
if (!currentSid) return;
|
| 468 |
sessions[currentSid].messages = [];
|
| 469 |
sessions[currentSid].title = "New Chat";
|
| 470 |
saveSessions();
|
|
|
|
| 1 |
/**
|
| 2 |
* ORBIT β Educational Research Assistant
|
| 3 |
+
* V2 SCRIPT: Anti-Crash, Safe Storage, Full Logic
|
| 4 |
*/
|
| 5 |
|
| 6 |
// βββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 13 |
};
|
| 14 |
|
| 15 |
// βββββββββββββββββββββββββββββββββββββ
|
| 16 |
+
// 2. State & Data (GANTI KEY KE V2 BIAR BERSIH)
|
| 17 |
// βββββββββββββββββββββββββββββββββββββ
|
| 18 |
let currentUser = null;
|
| 19 |
let appSettings = null;
|
| 20 |
+
// Pakai key baru "orbit_sessions_v2" untuk menghindari data korup lama
|
| 21 |
+
let sessions = JSON.parse(localStorage.getItem('orbit_sessions_v2')) || {};
|
| 22 |
let currentSid = null;
|
| 23 |
let pdfText = null;
|
| 24 |
let pdfFilename = null;
|
|
|
|
| 51 |
} catch (error) {
|
| 52 |
console.error("Init Error:", error);
|
| 53 |
} finally {
|
|
|
|
| 54 |
populateModelSelect();
|
| 55 |
|
| 56 |
+
// Bersihkan data jika bentuknya Array (korup dari versi lama)
|
| 57 |
+
if (Array.isArray(sessions)) {
|
| 58 |
+
sessions = {};
|
| 59 |
+
saveSessions();
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
const ids = Object.keys(sessions).sort((a, b) => Number(b) - Number(a));
|
| 63 |
if (ids.length > 0) {
|
| 64 |
loadSession(ids[0]);
|
|
|
|
| 72 |
// 4. Session & History Logic
|
| 73 |
// βββββββββββββββββββββββββββββββββββββ
|
| 74 |
function saveSessions() {
|
| 75 |
+
localStorage.setItem('orbit_sessions_v2', JSON.stringify(sessions));
|
| 76 |
}
|
| 77 |
|
| 78 |
function newSession() {
|
|
|
|
| 84 |
function loadSession(id) {
|
| 85 |
if (!sessions[id]) return;
|
| 86 |
currentSid = id;
|
| 87 |
+
|
| 88 |
+
// Sabuk pengaman: Pastikan array messages selalu ada
|
| 89 |
+
if (!sessions[currentSid].messages) {
|
| 90 |
+
sessions[currentSid].messages = [];
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
if(chatMessages) chatMessages.innerHTML = '';
|
| 94 |
|
| 95 |
const msgs = sessions[id].messages || [];
|
|
|
|
| 125 |
return `
|
| 126 |
<button onclick="window.loadSession('${id}')" class="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition-all text-left truncate ${activeClass}">
|
| 127 |
<svg class="w-4 h-4 shrink-0 opacity-60" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
| 128 |
+
<span class="truncate">${sessions[id].title || "New Chat"}</span>
|
| 129 |
</button>`;
|
| 130 |
}).join('');
|
| 131 |
}
|
| 132 |
|
| 133 |
function appendMessage(role, content, displayContent) {
|
| 134 |
+
if(!sessions[currentSid]) newSession();
|
| 135 |
+
if(!sessions[currentSid].messages) sessions[currentSid].messages = [];
|
| 136 |
+
|
| 137 |
sessions[currentSid].messages.push({ role, content, displayContent });
|
| 138 |
|
| 139 |
if (role === "user" && sessions[currentSid].messages.filter(m => m.role === "user").length === 1) {
|
|
|
|
| 157 |
if(chatMessages) chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 158 |
}
|
| 159 |
|
|
|
|
| 160 |
function simpleMarkdown(text) {
|
| 161 |
let h = String(text || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
| 162 |
h = h.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, c) => `<pre><code>${c.trim()}</code></pre>`);
|
|
|
|
| 235 |
if(chatTextarea) { chatTextarea.value = ""; chatTextarea.style.height = "auto"; }
|
| 236 |
if($("welcome-msg")) $("welcome-msg").remove();
|
| 237 |
|
| 238 |
+
// Safety check session
|
| 239 |
+
if (!sessions[currentSid] || !sessions[currentSid].messages) {
|
| 240 |
+
newSession();
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
setBusy(true);
|
| 244 |
renderBubble("user", displayPrompt);
|
| 245 |
appendMessage("user", fullPrompt, displayPrompt);
|
|
|
|
| 254 |
const payload = {
|
| 255 |
prompt: fullPrompt,
|
| 256 |
model: $("model-select") ? $("model-select").value : (appSettings?.current_model || ""),
|
| 257 |
+
messages: sessions[currentSid].messages.slice(0, -1) // Aman karena udah di-check di atas
|
| 258 |
};
|
| 259 |
|
| 260 |
const res = await fetch("/api/chat", {
|
|
|
|
| 359 |
if($("settings-modal")) $("settings-modal").classList.add("hidden");
|
| 360 |
}
|
| 361 |
|
| 362 |
+
addEvt("btn-open-settings", "click", openSettings);
|
|
|
|
| 363 |
addEvt("btn-close-settings", "click", closeSettings);
|
| 364 |
addEvt("btn-cancel-settings", "click", closeSettings);
|
| 365 |
|
|
|
|
| 408 |
appSettings = await res.json();
|
| 409 |
populateModelSelect();
|
| 410 |
closeSettings();
|
| 411 |
+
renderSys("Settings saved.");
|
| 412 |
} catch (err) {
|
| 413 |
+
renderSys(`Failed to save: ${err.message}`, true);
|
| 414 |
}
|
| 415 |
});
|
| 416 |
|
|
|
|
| 441 |
const res = await fetch("/api/validate_doi", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ doi }) });
|
| 442 |
const data = await res.json();
|
| 443 |
if (!res.ok || data.error) {
|
| 444 |
+
resDiv.innerHTML = `<p class="text-red-500 font-medium">Error: ${data.error || "Failed"}</p>`;
|
| 445 |
} else {
|
| 446 |
resDiv.innerHTML = `
|
| 447 |
<div class="space-y-2">
|
|
|
|
| 481 |
addEvt("btn-new-chat", "click", newSession);
|
| 482 |
|
| 483 |
function handleClear() {
|
| 484 |
+
if (!currentSid || !sessions[currentSid]) return;
|
| 485 |
sessions[currentSid].messages = [];
|
| 486 |
sessions[currentSid].title = "New Chat";
|
| 487 |
saveSessions();
|