Update static/js/script.js
Browse files- static/js/script.js +47 -23
static/js/script.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
/**
|
| 2 |
* ORBIT – Educational Research Assistant
|
| 3 |
-
* FULL V-MASTER SCRIPT -
|
| 4 |
*/
|
| 5 |
|
| 6 |
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 14 |
let isBusy = false;
|
| 15 |
let pdfText = ""; let pdfFilename = "";
|
| 16 |
|
| 17 |
-
// 14 MODEL FREE
|
| 18 |
const DEFAULT_OR_MODELS = [
|
| 19 |
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
| 20 |
"baidu/cobuddy:free",
|
|
@@ -32,21 +32,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 32 |
"meta-llama/llama-3.3-70b-instruct:free"
|
| 33 |
];
|
| 34 |
|
| 35 |
-
// TOAST NOTIFICATION (
|
| 36 |
function showToast(message, isError = false) {
|
| 37 |
let toast = $('orbit-toast');
|
| 38 |
if (!toast) {
|
| 39 |
toast = document.createElement('div');
|
| 40 |
toast.id = 'orbit-toast';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
document.body.appendChild(toast);
|
| 42 |
}
|
| 43 |
-
|
| 44 |
-
toast.
|
| 45 |
-
toast.
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
|
| 52 |
// 1. DATA RECOVERY
|
|
@@ -79,10 +95,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 79 |
}
|
| 80 |
});
|
| 81 |
|
| 82 |
-
// 3. BOOT & INIT (
|
| 83 |
async function init() {
|
| 84 |
try {
|
| 85 |
-
// cache: 'no-store' WAJIB biar gak baca API nyangkut
|
| 86 |
const me = await fetch('/api/me', { cache: 'no-store' });
|
| 87 |
if(me.status === 401) { window.location.href = '/login'; return; }
|
| 88 |
if(me.ok) {
|
|
@@ -100,12 +115,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 100 |
appSettings.models_agentrouter = safeArr(appSettings.models_agentrouter);
|
| 101 |
appSettings.models_openai = safeArr(appSettings.models_openai);
|
| 102 |
|
| 103 |
-
|
| 104 |
-
if (!localStorage.getItem('orbit_force_free_v4')) {
|
| 105 |
appSettings.models_openrouter = [...DEFAULT_OR_MODELS];
|
| 106 |
-
localStorage.setItem('
|
| 107 |
|
| 108 |
-
// Langsung tembak ke backend biar nyimpen
|
| 109 |
fetch('/api/settings', {
|
| 110 |
method: 'POST',
|
| 111 |
headers: {'Content-Type': 'application/json'},
|
|
@@ -299,11 +312,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 299 |
bindAdd('btn-add-ar', 'inp-ar', 'models_agentrouter');
|
| 300 |
bindAdd('btn-add-oai', 'inp-oai', 'models_openai');
|
| 301 |
|
| 302 |
-
//
|
| 303 |
addEvt('btn-save-settings', async () => {
|
| 304 |
const btn = $('btn-save-settings');
|
| 305 |
const originalText = btn.textContent;
|
| 306 |
-
|
|
|
|
|
|
|
| 307 |
btn.disabled = true;
|
| 308 |
|
| 309 |
const payload = {
|
|
@@ -317,26 +332,35 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 317 |
models_openai: safeArr(appSettings.models_openai),
|
| 318 |
current_model: $('model-select').value
|
| 319 |
};
|
|
|
|
| 320 |
try {
|
| 321 |
const res = await fetch('/api/settings', {
|
| 322 |
method: 'POST',
|
| 323 |
headers: {'Content-Type': 'application/json'},
|
| 324 |
body: JSON.stringify(payload)
|
| 325 |
});
|
|
|
|
| 326 |
if(res.ok) {
|
| 327 |
appSettings = await res.json();
|
| 328 |
populateModelSelect();
|
| 329 |
-
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
} else {
|
| 332 |
-
throw new Error("Gagal
|
| 333 |
}
|
| 334 |
} catch(e) {
|
| 335 |
console.error(e);
|
| 336 |
showToast(`Error: ${e.message}`, true);
|
| 337 |
} finally {
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
| 340 |
}
|
| 341 |
});
|
| 342 |
|
|
|
|
| 1 |
/**
|
| 2 |
* ORBIT – Educational Research Assistant
|
| 3 |
+
* FULL V-MASTER SCRIPT - TOAST FIXED (VANILLA CSS), BUTTON SPINNER, DEEP ANALYZED
|
| 4 |
*/
|
| 5 |
|
| 6 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
| 14 |
let isBusy = false;
|
| 15 |
let pdfText = ""; let pdfFilename = "";
|
| 16 |
|
| 17 |
+
// 14 MODEL FREE
|
| 18 |
const DEFAULT_OR_MODELS = [
|
| 19 |
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
| 20 |
"baidu/cobuddy:free",
|
|
|
|
| 32 |
"meta-llama/llama-3.3-70b-instruct:free"
|
| 33 |
];
|
| 34 |
|
| 35 |
+
// TOAST NOTIFICATION (DIJAMIN MUNCUL - MURNI VANILLA CSS)
|
| 36 |
function showToast(message, isError = false) {
|
| 37 |
let toast = $('orbit-toast');
|
| 38 |
if (!toast) {
|
| 39 |
toast = document.createElement('div');
|
| 40 |
toast.id = 'orbit-toast';
|
| 41 |
+
// Styling murni JS biar nggak diblokir Tailwind CDN
|
| 42 |
+
toast.style.cssText = `
|
| 43 |
+
position: fixed; top: 24px; left: 50%; transform: translate(-50%, -20px);
|
| 44 |
+
padding: 12px 24px; border-radius: 50px; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
| 45 |
+
font-size: 14px; font-weight: 600; color: white; z-index: 99999;
|
| 46 |
+
opacity: 0; transition: all 0.3s ease-in-out; display: flex; align-items: center; gap: 8px;
|
| 47 |
+
pointer-events: none;
|
| 48 |
+
`;
|
| 49 |
document.body.appendChild(toast);
|
| 50 |
}
|
| 51 |
+
|
| 52 |
+
toast.style.backgroundColor = isError ? '#ef4444' : '#10b981'; // Merah / Hijau
|
| 53 |
+
toast.innerHTML = isError ? `<span>❌</span> <span>${message}</span>` : `<span>✅</span> <span>${message}</span>`;
|
| 54 |
+
|
| 55 |
+
// Animasi Masuk
|
| 56 |
+
setTimeout(() => {
|
| 57 |
+
toast.style.opacity = '1';
|
| 58 |
+
toast.style.transform = 'translate(-50%, 0)';
|
| 59 |
+
}, 10);
|
| 60 |
+
|
| 61 |
+
// Animasi Keluar
|
| 62 |
+
setTimeout(() => {
|
| 63 |
+
toast.style.opacity = '0';
|
| 64 |
+
toast.style.transform = 'translate(-50%, -20px)';
|
| 65 |
+
}, 3000);
|
| 66 |
}
|
| 67 |
|
| 68 |
// 1. DATA RECOVERY
|
|
|
|
| 95 |
}
|
| 96 |
});
|
| 97 |
|
| 98 |
+
// 3. BOOT & INIT (CACHE BUSTER AKTIF)
|
| 99 |
async function init() {
|
| 100 |
try {
|
|
|
|
| 101 |
const me = await fetch('/api/me', { cache: 'no-store' });
|
| 102 |
if(me.status === 401) { window.location.href = '/login'; return; }
|
| 103 |
if(me.ok) {
|
|
|
|
| 115 |
appSettings.models_agentrouter = safeArr(appSettings.models_agentrouter);
|
| 116 |
appSettings.models_openai = safeArr(appSettings.models_openai);
|
| 117 |
|
| 118 |
+
if (!localStorage.getItem('orbit_force_free_v5')) {
|
|
|
|
| 119 |
appSettings.models_openrouter = [...DEFAULT_OR_MODELS];
|
| 120 |
+
localStorage.setItem('orbit_force_free_v5', 'true');
|
| 121 |
|
|
|
|
| 122 |
fetch('/api/settings', {
|
| 123 |
method: 'POST',
|
| 124 |
headers: {'Content-Type': 'application/json'},
|
|
|
|
| 312 |
bindAdd('btn-add-ar', 'inp-ar', 'models_agentrouter');
|
| 313 |
bindAdd('btn-add-oai', 'inp-oai', 'models_openai');
|
| 314 |
|
| 315 |
+
// MANTAP: ANIMASI BUTTON SAVE + TOAST IJO + AUTO CLOSE
|
| 316 |
addEvt('btn-save-settings', async () => {
|
| 317 |
const btn = $('btn-save-settings');
|
| 318 |
const originalText = btn.textContent;
|
| 319 |
+
|
| 320 |
+
// Ubah tombol jadi "Saving..." plus spinner bulet
|
| 321 |
+
btn.innerHTML = `<svg class="w-4 h-4 animate-spin inline mr-2" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path></svg>Saving...`;
|
| 322 |
btn.disabled = true;
|
| 323 |
|
| 324 |
const payload = {
|
|
|
|
| 332 |
models_openai: safeArr(appSettings.models_openai),
|
| 333 |
current_model: $('model-select').value
|
| 334 |
};
|
| 335 |
+
|
| 336 |
try {
|
| 337 |
const res = await fetch('/api/settings', {
|
| 338 |
method: 'POST',
|
| 339 |
headers: {'Content-Type': 'application/json'},
|
| 340 |
body: JSON.stringify(payload)
|
| 341 |
});
|
| 342 |
+
|
| 343 |
if(res.ok) {
|
| 344 |
appSettings = await res.json();
|
| 345 |
populateModelSelect();
|
| 346 |
+
|
| 347 |
+
// Kasih delay dikit biar kerasa "kerja", terus tutup & munculin Notif
|
| 348 |
+
setTimeout(() => {
|
| 349 |
+
$('settings-modal').classList.add('hidden');
|
| 350 |
+
showToast("Settings saved successfully!");
|
| 351 |
+
}, 300);
|
| 352 |
+
|
| 353 |
} else {
|
| 354 |
+
throw new Error("Gagal terhubung ke database server");
|
| 355 |
}
|
| 356 |
} catch(e) {
|
| 357 |
console.error(e);
|
| 358 |
showToast(`Error: ${e.message}`, true);
|
| 359 |
} finally {
|
| 360 |
+
setTimeout(() => {
|
| 361 |
+
btn.textContent = originalText;
|
| 362 |
+
btn.disabled = false;
|
| 363 |
+
}, 300);
|
| 364 |
}
|
| 365 |
});
|
| 366 |
|