xenux4u commited on
Commit
66bdef6
·
verified ·
1 Parent(s): 8385d0d

Update static/js/script.js

Browse files
Files changed (1) hide show
  1. static/js/script.js +65 -8
static/js/script.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * ORBIT – Educational Research Assistant
3
- * FULL V-MASTER SCRIPT - ZERO CUTS, ZERO BUGS
4
  */
5
 
6
  document.addEventListener('DOMContentLoaded', () => {
@@ -14,6 +14,38 @@ document.addEventListener('DOMContentLoaded', () => {
14
  let isBusy = false;
15
  let pdfText = ""; let pdfFilename = "";
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  // 1. DATA RECOVERY
18
  try {
19
  const stored = localStorage.getItem('orbit_sessions_v12');
@@ -21,7 +53,7 @@ document.addEventListener('DOMContentLoaded', () => {
21
  if (typeof sessions !== 'object' || Array.isArray(sessions)) sessions = {};
22
  } catch(e) { sessions = {}; }
23
 
24
- // 2. PWA INSTALL LOGIC (Always show button if not installed)
25
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
26
  let deferredPrompt;
27
 
@@ -61,7 +93,13 @@ document.addEventListener('DOMContentLoaded', () => {
61
  const setRes = await fetch('/api/settings');
62
  if(setRes.ok) {
63
  appSettings = await setRes.json();
 
 
 
 
64
  appSettings.models_openrouter = safeArr(appSettings.models_openrouter);
 
 
65
  appSettings.models_nvidia = safeArr(appSettings.models_nvidia);
66
  appSettings.models_gemini = safeArr(appSettings.models_gemini);
67
  appSettings.models_agentrouter = safeArr(appSettings.models_agentrouter);
@@ -88,7 +126,7 @@ document.addEventListener('DOMContentLoaded', () => {
88
  const cm = $('chat-messages');
89
  const ws = $('welcome-msg');
90
 
91
- if(cm) cm.innerHTML = ''; // HANYA menghapus isi chat, TIDAK menghapus welcome-msg karena beda div!
92
 
93
  if(sessions[id].messages && sessions[id].messages.length > 0) {
94
  if(ws) ws.classList.add('hidden');
@@ -174,7 +212,7 @@ document.addEventListener('DOMContentLoaded', () => {
174
  addEvt('chat-textarea', 'keydown', e => { if(e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); } });
175
  if($('chat-textarea')) $('chat-textarea').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 160) + 'px'; });
176
 
177
- // 6. SETTINGS MODAL
178
  const provMap = { "OpenRouter": "or", "Nvidia NIM": "nv", "Google Gemini": "gem", "AgentRouter": "ar", "Custom OpenAI": "oai" };
179
 
180
  function syncProviderUI() {
@@ -251,7 +289,13 @@ document.addEventListener('DOMContentLoaded', () => {
251
  bindAdd('btn-add-ar', 'inp-ar', 'models_agentrouter');
252
  bindAdd('btn-add-oai', 'inp-oai', 'models_openai');
253
 
 
254
  addEvt('btn-save-settings', async () => {
 
 
 
 
 
255
  const payload = {
256
  provider: $('settings-provider').value,
257
  base_url: $('settings-url').value,
@@ -265,11 +309,25 @@ document.addEventListener('DOMContentLoaded', () => {
265
  };
266
  try {
267
  const res = await fetch('/api/settings', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) });
268
- if(res.ok) { appSettings = await res.json(); populateModelSelect(); $('settings-modal').classList.add('hidden'); }
269
- } catch(e) { console.error(e); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  });
271
 
272
- // 7. DOI MODAL (FIXED EVENTS)
273
  addEvt('btn-doi', 'click', () => {
274
  $('doi-modal').classList.remove('hidden');
275
  if($('doi-input')) { $('doi-input').value = ""; $('doi-input').focus(); }
@@ -278,7 +336,6 @@ document.addEventListener('DOMContentLoaded', () => {
278
  });
279
  addEvt('btn-close-doi', 'click', () => $('doi-modal').classList.add('hidden'));
280
 
281
- // Bind enter key on input to the submit button
282
  if($('doi-input')) {
283
  $('doi-input').addEventListener('keydown', e => {
284
  if(e.key === 'Enter') {
 
1
  /**
2
  * ORBIT – Educational Research Assistant
3
+ * FULL V-MASTER SCRIPT - WITH TOAST NOTIFICATION & AUTO CLOSE
4
  */
5
 
6
  document.addEventListener('DOMContentLoaded', () => {
 
14
  let isBusy = false;
15
  let pdfText = ""; let pdfFilename = "";
16
 
17
+ // --- FITUR BARU: TOAST NOTIFICATION ---
18
+ function showToast(message, isError = false) {
19
+ let toast = $('orbit-toast');
20
+ if (!toast) {
21
+ toast = document.createElement('div');
22
+ toast.id = 'orbit-toast';
23
+ document.body.appendChild(toast);
24
+ }
25
+ // Styling Toast dengan Tailwind
26
+ const bgClass = isError ? 'bg-red-500' : 'bg-emerald-500';
27
+ const icon = isError
28
+ ? `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>`
29
+ : `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`;
30
+
31
+ toast.className = `fixed top-6 left-1/2 transform -translate-x-1/2 px-5 py-2.5 rounded-full shadow-2xl text-sm font-semibold z-[9999] transition-all duration-300 flex items-center gap-2 text-white ${bgClass}`;
32
+ toast.style.opacity = '0';
33
+ toast.style.transform = 'translate(-50%, -20px)';
34
+ toast.innerHTML = `${icon} <span>${message}</span>`;
35
+
36
+ // Animasi Masuk
37
+ requestAnimationFrame(() => {
38
+ toast.style.opacity = '1';
39
+ toast.style.transform = 'translate(-50%, 0)';
40
+ });
41
+
42
+ // Animasi Keluar setelah 3 detik
43
+ setTimeout(() => {
44
+ toast.style.opacity = '0';
45
+ toast.style.transform = 'translate(-50%, -20px)';
46
+ }, 3000);
47
+ }
48
+
49
  // 1. DATA RECOVERY
50
  try {
51
  const stored = localStorage.getItem('orbit_sessions_v12');
 
53
  if (typeof sessions !== 'object' || Array.isArray(sessions)) sessions = {};
54
  } catch(e) { sessions = {}; }
55
 
56
+ // 2. PWA INSTALL LOGIC
57
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
58
  let deferredPrompt;
59
 
 
93
  const setRes = await fetch('/api/settings');
94
  if(setRes.ok) {
95
  appSettings = await setRes.json();
96
+
97
+ // Inject Default Free Models untuk OpenRouter jika kosong
98
+ const DEFAULT_OR_MODELS = ["nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free", "baidu/cobuddy:free", "inclusionai/ring-2.6-1t:free", "poolside/laguna-xs.2:free", "google/gemma-4-26b-a4b-it:free", "google/gemma-4-31b-it:free", "nvidia/llama-nemotron-embed-vl-1b-v2:free", "minimax/minimax-m2.5:free", "openai/gpt-oss-120b:free", "liquid/lfm-2.5-1.2b-thinking:free", "liquid/lfm-2.5-1.2b-instruct:free", "qwen/qwen3-next-80b-a3b-instruct:free", "z-ai/glm-4.5-air:free", "meta-llama/llama-3.3-70b-instruct:free"];
99
+
100
  appSettings.models_openrouter = safeArr(appSettings.models_openrouter);
101
+ if(appSettings.models_openrouter.length === 0) appSettings.models_openrouter = [...DEFAULT_OR_MODELS];
102
+
103
  appSettings.models_nvidia = safeArr(appSettings.models_nvidia);
104
  appSettings.models_gemini = safeArr(appSettings.models_gemini);
105
  appSettings.models_agentrouter = safeArr(appSettings.models_agentrouter);
 
126
  const cm = $('chat-messages');
127
  const ws = $('welcome-msg');
128
 
129
+ if(cm) cm.innerHTML = '';
130
 
131
  if(sessions[id].messages && sessions[id].messages.length > 0) {
132
  if(ws) ws.classList.add('hidden');
 
212
  addEvt('chat-textarea', 'keydown', e => { if(e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); } });
213
  if($('chat-textarea')) $('chat-textarea').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 160) + 'px'; });
214
 
215
+ // 6. SETTINGS MODAL & DYNAMIC UI
216
  const provMap = { "OpenRouter": "or", "Nvidia NIM": "nv", "Google Gemini": "gem", "AgentRouter": "ar", "Custom OpenAI": "oai" };
217
 
218
  function syncProviderUI() {
 
289
  bindAdd('btn-add-ar', 'inp-ar', 'models_agentrouter');
290
  bindAdd('btn-add-oai', 'inp-oai', 'models_openai');
291
 
292
+ // --- SAVE SETTINGS DENGAN TOAST & AUTO CLOSE ---
293
  addEvt('btn-save-settings', async () => {
294
+ const btn = $('btn-save-settings');
295
+ const originalText = btn.textContent;
296
+ btn.textContent = "Saving...";
297
+ btn.disabled = true;
298
+
299
  const payload = {
300
  provider: $('settings-provider').value,
301
  base_url: $('settings-url').value,
 
309
  };
310
  try {
311
  const res = await fetch('/api/settings', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) });
312
+ if(res.ok) {
313
+ appSettings = await res.json();
314
+ populateModelSelect();
315
+ $('settings-modal').classList.add('hidden'); // Auto close
316
+ showToast("Settings saved successfully!"); // Notifikasi Toast hijau
317
+ } else {
318
+ const errData = await res.json();
319
+ throw new Error(errData.error || "Gagal menyimpan");
320
+ }
321
+ } catch(e) {
322
+ console.error(e);
323
+ showToast(`Error: ${e.message}`, true); // Notifikasi Toast merah
324
+ } finally {
325
+ btn.textContent = originalText;
326
+ btn.disabled = false;
327
+ }
328
  });
329
 
330
+ // 7. DOI MODAL
331
  addEvt('btn-doi', 'click', () => {
332
  $('doi-modal').classList.remove('hidden');
333
  if($('doi-input')) { $('doi-input').value = ""; $('doi-input').focus(); }
 
336
  });
337
  addEvt('btn-close-doi', 'click', () => $('doi-modal').classList.add('hidden'));
338
 
 
339
  if($('doi-input')) {
340
  $('doi-input').addEventListener('keydown', e => {
341
  if(e.key === 'Enter') {