Devmightwin commited on
Commit
dcaafe1
·
verified ·
1 Parent(s): 946cb35

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +388 -214
app.js CHANGED
@@ -51,6 +51,18 @@ const userPlan = document.getElementById('user-plan');
51
  const upgradeBtn = document.getElementById('upgrade-btn');
52
  const syncStatus = document.getElementById('sync-status');
53
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  // Upgrade Modal Elements
55
  const upgradeModal = document.getElementById('upgrade-modal');
56
  const closeUpgrade = document.getElementById('close-upgrade');
@@ -71,82 +83,7 @@ const optRom = document.getElementById('opt-rom');
71
  const personaInput = document.getElementById('persona-input');
72
  const saveSettingsBtn = document.getElementById('save-settings');
73
 
74
- // Model Selector Elements
75
- const modelSelectBtn = document.getElementById('model-select-btn');
76
- const modelDropdown = document.getElementById('model-dropdown');
77
- const currentModelDisplay = document.getElementById('current-model-display');
78
- const selectOrki = document.getElementById('select-orki');
79
- const selectOkafwr = document.getElementById('select-okafwr');
80
- const okafwrLockIcon = document.getElementById('okafwr-lock-icon');
81
-
82
- let selectedModel = 'orki-3.0';
83
-
84
- // Auto-expand textarea & handle Enter to submit
85
- if (input) {
86
- input.addEventListener('input', function () {
87
- this.style.height = 'auto';
88
- this.style.height = Math.min(this.scrollHeight, 112) + 'px';
89
- });
90
-
91
- input.addEventListener('keydown', function (e) {
92
- if (e.key === 'Enter' && !e.shiftKey) {
93
- e.preventDefault();
94
- handleSend();
95
- }
96
- });
97
- }
98
-
99
-
100
-
101
- // Toggle Dropdown
102
- if (modelSelectBtn) {
103
- modelSelectBtn.onclick = (e) => {
104
- e.stopPropagation();
105
- modelDropdown.classList.toggle('hidden');
106
- };
107
- }
108
-
109
- document.addEventListener('click', () => {
110
- if (modelDropdown && !modelDropdown.classList.contains('hidden')) {
111
- modelDropdown.classList.add('hidden');
112
- }
113
- });
114
-
115
- // Select Orki 3.0
116
- if (selectOrki) {
117
- selectOrki.onclick = () => {
118
- selectedModel = 'orki-3.0';
119
- currentModelDisplay.textContent = 'Orki 3.0';
120
- currentModelDisplay.className = 'text-zinc-200';
121
- modelDropdown.classList.add('hidden');
122
- };
123
- }
124
-
125
- // Select Okafwr 2.1 (Triggers upgrade modal if not Pro)
126
- if (selectOkafwr) {
127
- selectOkafwr.onclick = () => {
128
- if (currentPlan !== 'Pro') {
129
- modelDropdown.classList.add('hidden');
130
- upgradeModal.classList.remove('hidden');
131
- alert("Okafwr 2.1 requires an active Pro subscription.");
132
- return;
133
- }
134
- selectedModel = 'okafwr-2.1';
135
- currentModelDisplay.textContent = 'Okafwr 2.1 ⚡';
136
- currentModelDisplay.className = 'text-amber-400 font-bold';
137
- modelDropdown.classList.add('hidden');
138
- };
139
- }
140
-
141
- // Update lock icon when plan state changes
142
- function updateModelLockUI() {
143
- if (okafwrLockIcon) {
144
- okafwrLockIcon.textContent = currentPlan === 'Pro' ? '✓' : '🔒';
145
- okafwrLockIcon.className = currentPlan === 'Pro' ? 'text-amber-400 text-xs font-bold' : 'text-zinc-500 text-xs';
146
- }
147
- }
148
-
149
-
150
  function safeGet(key, fallback) {
151
  try { return localStorage.getItem(key) || fallback; } catch (e) { return fallback; }
152
  }
@@ -154,18 +91,32 @@ function safeSet(key, val) {
154
  try { localStorage.setItem(key, val); } catch (e) {}
155
  }
156
 
 
 
 
 
 
157
  let currentScript = safeGet('arki_script', 'deva');
158
  let userPersona = safeGet('arki_persona', '');
159
  let currentUser = null;
160
  let currentPlan = "Free";
 
 
161
  let isAuthRegister = false;
162
  let abortController = null;
163
  let isGenerating = false;
164
  let currentChatId = null;
165
  let currentChatMessages = [];
166
  let selectedCycle = 'monthly';
 
 
 
 
 
 
 
 
167
 
168
- // Pricing Matrix
169
  const PRICING = {
170
  weekly: { plus: 29, pro: 59, plusPaise: 2900, proPaise: 5900, label: '/wk' },
171
  monthly: { plus: 99, pro: 199, plusPaise: 9900, proPaise: 19900, label: '/mo' },
@@ -181,8 +132,10 @@ function renderMarkdown(text) {
181
  return text.replace(/\n/g, '<br>');
182
  }
183
 
 
184
  function setGeneratingState(generating) {
185
  isGenerating = generating;
 
186
  if (generating) {
187
  sendBtn.classList.remove('bg-emerald-600', 'hover:bg-emerald-500');
188
  sendBtn.classList.add('bg-red-600', 'hover:bg-red-500');
@@ -202,14 +155,53 @@ function setGeneratingState(generating) {
202
  }
203
  }
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  function toggleDrawer(open) {
 
206
  drawer.style.transform = open ? 'translateX(0%)' : 'translateX(-100%)';
207
  drawerBackdrop.classList.toggle('hidden', !open);
208
  }
209
 
210
- menuBtn.onclick = () => toggleDrawer(true);
211
- closeDrawer.onclick = () => toggleDrawer(false);
212
- drawerBackdrop.onclick = () => toggleDrawer(false);
213
 
214
  function createWelcomeScreen() {
215
  const div = document.createElement('div');
@@ -226,14 +218,74 @@ function startNewChat() {
226
  if (isGenerating && abortController) abortController.abort();
227
  currentChatId = null;
228
  currentChatMessages = [];
229
- chat.innerHTML = '';
230
- chat.appendChild(createWelcomeScreen());
 
 
231
  toggleDrawer(false);
232
  renderHistorySidebar();
233
  }
234
 
235
- newChatBtn.onclick = startNewChat;
 
 
 
 
 
 
 
 
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  function updatePricingUI() {
238
  const p = PRICING[selectedCycle];
239
  if (plusPriceText) plusPriceText.innerHTML = `₹${p.plus}<span class="text-xs text-zinc-500 font-normal">${p.label}</span>`;
@@ -251,11 +303,13 @@ if (cycleWeekly) cycleWeekly.onclick = () => { selectedCycle = 'weekly'; updateP
251
  if (cycleMonthly) cycleMonthly.onclick = () => { selectedCycle = 'monthly'; updatePricingUI(); };
252
  if (cycleYearly) cycleYearly.onclick = () => { selectedCycle = 'yearly'; updatePricingUI(); };
253
 
254
- upgradeBtn.onclick = () => {
255
- if (!currentUser) return;
256
- if (upgradeModal) upgradeModal.classList.remove('hidden');
257
- toggleDrawer(false);
258
- };
 
 
259
 
260
  if (closeUpgrade) closeUpgrade.onclick = () => upgradeModal.classList.add('hidden');
261
 
@@ -268,7 +322,7 @@ function initiatePayment(planTier, amountPaise) {
268
  currency: "INR",
269
  name: "Orki AI",
270
  description: `${planTier} Plan (${selectedCycle})`,
271
- prefill: { email: currentUser.email, contact: "9999999999" },
272
  theme: { color: planTier === "Pro" ? "#f59e0b" : "#059669" },
273
  handler: async function (res) {
274
  try {
@@ -291,6 +345,8 @@ function initiatePayment(planTier, amountPaise) {
291
  userPlan.className = planTier === "Pro" ? "text-[11px] font-semibold text-amber-400" : "text-[11px] font-semibold text-emerald-400";
292
  upgradeBtn.textContent = `${planTier} Active`;
293
  upgradeBtn.disabled = true;
 
 
294
  alert(`Success! You are now subscribed to ${planTier} until ${expiresAt.toLocaleDateString()}.`);
295
  } catch (err) {
296
  alert("Activation failed: " + err.message);
@@ -306,15 +362,19 @@ if (buyPlusBtn) buyPlusBtn.onclick = () => initiatePayment("Plus", PRICING[selec
306
  if (buyProBtn) buyProBtn.onclick = () => initiatePayment("Pro", PRICING[selectedCycle].proPaise);
307
 
308
  // Settings Controls
309
- openSettingsBtn.onclick = () => {
310
- toggleDrawer(false);
311
- personaInput.value = userPersona;
312
- updateScriptUI();
313
- settingsModal.classList.remove('hidden');
314
- };
315
- closeSettingsBtn.onclick = () => settingsModal.classList.add('hidden');
 
 
 
316
 
317
  function updateScriptUI() {
 
318
  if (currentScript === 'deva') {
319
  optDeva.className = "py-2 rounded-lg text-xs font-semibold bg-emerald-600 text-white transition cursor-pointer";
320
  optRom.className = "py-2 rounded-lg text-xs font-medium text-zinc-400 hover:text-white transition cursor-pointer";
@@ -324,16 +384,19 @@ function updateScriptUI() {
324
  }
325
  }
326
 
327
- optDeva.onclick = () => { currentScript = 'deva'; updateScriptUI(); };
328
- optRom.onclick = () => { currentScript = 'roman'; updateScriptUI(); };
329
 
330
- saveSettingsBtn.onclick = () => {
331
- userPersona = personaInput.value.trim();
332
- safeSet('arki_script', currentScript);
333
- safeSet('arki_persona', userPersona);
334
- settingsModal.classList.add('hidden');
335
- };
 
 
336
 
 
337
  function createBubbleElement(role) {
338
  const wrapper = document.createElement('div');
339
  wrapper.className = `flex ${role === 'user' ? 'justify-end' : 'justify-start'}`;
@@ -342,16 +405,17 @@ function createBubbleElement(role) {
342
  ? 'bg-emerald-600 text-white rounded-2xl rounded-tr-xs px-4 py-2.5 max-w-[85%] text-[15px] whitespace-pre-wrap leading-relaxed shadow-sm break-words [overflow-wrap:anywhere]'
343
  : 'bg-[#242427] border border-zinc-700/40 text-zinc-100 rounded-2xl rounded-tl-xs px-4 py-2.5 max-w-[85%] text-[15px] shadow-sm leading-relaxed prose-custom break-words [overflow-wrap:anywhere]';
344
  wrapper.appendChild(bubble);
345
- chat.appendChild(wrapper);
346
- chat.scrollTop = chat.scrollHeight;
 
 
347
  return bubble;
348
  }
349
 
350
-
351
  function appendMessage(role, text) {
352
  const bubble = createBubbleElement(role);
353
  bubble.innerHTML = role === 'user' ? text : renderMarkdown(text);
354
- chat.scrollTop = chat.scrollHeight;
355
  }
356
 
357
  // Audio Recording
@@ -359,10 +423,12 @@ let mediaRecorder = null;
359
  let audioChunks = [];
360
  let isRecording = false;
361
 
362
- micBtn.onclick = () => {
363
- if (isRecording) stopRecording();
364
- else startRecording();
365
- };
 
 
366
 
367
  async function startRecording() {
368
  if (isGenerating) return;
@@ -395,8 +461,10 @@ async function startRecording() {
395
  function stopRecording() {
396
  if (mediaRecorder && mediaRecorder.state !== 'inactive') mediaRecorder.stop();
397
  isRecording = false;
398
- micBtn.classList.remove('bg-red-600', 'text-white', 'animate-pulse');
399
- micBtn.classList.add('bg-zinc-800', 'text-zinc-300');
 
 
400
  }
401
 
402
  function blobToBase64(blob) {
@@ -408,8 +476,33 @@ function blobToBase64(blob) {
408
  });
409
  }
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  async function handleAudioSend(audioBlob, mimeType) {
412
  if (isGenerating) return;
 
 
 
 
 
 
 
 
413
  const welcome = document.getElementById('welcome-screen');
414
  if (welcome) welcome.remove();
415
 
@@ -417,7 +510,7 @@ async function handleAudioSend(audioBlob, mimeType) {
417
  userBubble.textContent = '🎙️ Transcribing...';
418
  currentChatMessages.push({ role: 'user', text: '[Voice Note]' });
419
  setGeneratingState(true);
420
- thinking.classList.remove('hidden');
421
 
422
  const base64Audio = await blobToBase64(audioBlob);
423
  const scriptConstraint = currentScript === 'roman'
@@ -447,15 +540,16 @@ async function handleAudioSend(audioBlob, mimeType) {
447
  signal: abortController.signal,
448
  body: JSON.stringify({
449
  plan: currentPlan,
 
450
  script: currentScript,
451
  userPersona: userPersona,
452
  contents: apiHistory,
453
- generationConfig: { temperature: 0.4, maxOutputTokens: 2000 }
454
  })
455
  });
456
 
457
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
458
- thinking.classList.add('hidden');
459
  const aiBubble = createBubbleElement('model');
460
  const reader = res.body.getReader();
461
  const decoder = new TextDecoder('utf-8');
@@ -490,7 +584,7 @@ async function handleAudioSend(audioBlob, mimeType) {
490
  } else {
491
  aiBubble.innerHTML = renderMarkdown(fullAiText);
492
  }
493
- chat.scrollTop = chat.scrollHeight;
494
  }
495
  } catch (e) {}
496
  }
@@ -502,18 +596,20 @@ async function handleAudioSend(audioBlob, mimeType) {
502
  currentChatMessages[currentChatMessages.length - 1].text = parts[0].replace('TRANSCRIPTION:', '').trim() || '[Voice Note]';
503
  currentChatMessages.push({ role: 'model', text: parts.slice(1).join('RESPONSE:').trim() });
504
  await saveCurrentThreadToCloud();
 
505
  } else if (fullAiText.trim()) {
506
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
507
  await saveCurrentThreadToCloud();
 
508
  }
509
  } catch (err) {
510
  if (err.name !== 'AbortError') {
511
- thinking.classList.add('hidden');
512
  userBubble.textContent = '🎙️ [Failed Voice Note]';
513
  appendMessage('model', `Voice processing error: ${err.message}`);
514
  }
515
  } finally {
516
- thinking.classList.add('hidden');
517
  setGeneratingState(false);
518
  abortController = null;
519
  }
@@ -521,25 +617,32 @@ async function handleAudioSend(audioBlob, mimeType) {
521
 
522
  // Text Send
523
  async function handleSend() {
524
-
525
  if (isGenerating) {
526
  if (abortController) abortController.abort();
527
  return;
528
  }
529
 
530
- const userText = input.value.trim();
 
 
 
 
 
 
 
531
  if (!userText) return;
532
 
533
  const welcome = document.getElementById('welcome-screen');
534
  if (welcome) welcome.remove();
535
-
536
-
537
  appendMessage('user', userText);
538
  currentChatMessages.push({ role: 'user', text: userText });
 
539
  input.value = '';
540
- input.style.height = 'auto'; // Resets textarea back to a single line
 
541
  setGeneratingState(true);
542
- thinking.classList.remove('hidden');
543
 
544
  const scriptConstraint = currentScript === 'roman'
545
  ? "\n[Constraint: Reply ONLY in Romanized Bodo ending in 'i' not 'y'. No Devanagari.]"
@@ -560,20 +663,20 @@ async function handleSend() {
560
  signal: abortController.signal,
561
  body: JSON.stringify({
562
  plan: currentPlan,
563
- script: currentScript,
564
  model: selectedModel,
 
565
  userPersona: userPersona,
566
  contents: apiHistory,
567
- generationConfig: { temperature: 0.5, maxOutputTokens: 2000 }
568
  })
569
  });
570
 
571
  if (!res.ok) {
572
  const errData = await res.json().catch(() => ({}));
573
- throw new Error(errData.error?.message || `HTTP ${res.status}`);
574
  }
575
 
576
- thinking.classList.add('hidden');
577
  const aiBubble = createBubbleElement('model');
578
  const reader = res.body.getReader();
579
  const decoder = new TextDecoder('utf-8');
@@ -597,7 +700,7 @@ async function handleSend() {
597
  if (chunk) {
598
  fullAiText += chunk;
599
  aiBubble.innerHTML = renderMarkdown(fullAiText);
600
- chat.scrollTop = chat.scrollHeight;
601
  }
602
  } catch (e) {}
603
  }
@@ -607,30 +710,35 @@ async function handleSend() {
607
  if (fullAiText.trim()) {
608
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
609
  await saveCurrentThreadToCloud();
 
610
  }
611
  } catch (err) {
612
  if (err.name === 'AbortError') {
613
  if (fullAiText.trim()) {
614
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
615
  await saveCurrentThreadToCloud();
 
616
  }
617
  } else {
618
- thinking.classList.add('hidden');
619
  appendMessage('model', err.message.includes('429') ? 'Rate limit hit (5 req/min). 15s नेनानै फिन लिर।' : `Error: ${err.message}`);
620
  }
621
  } finally {
622
- thinking.classList.add('hidden');
623
  setGeneratingState(false);
624
  abortController = null;
625
- input.focus();
626
  }
627
  }
628
 
629
- form.onsubmit = (e) => { e.preventDefault(); handleSend(); };
 
 
 
 
 
630
 
631
  // Cloud Firestore Threads
632
- let cachedThreads = [];
633
-
634
  async function saveCurrentThreadToCloud() {
635
  if (!currentUser || !db || currentChatMessages.length === 0) return;
636
  const firstPrompt = currentChatMessages.find(m => m.role === 'user')?.text || "New Conversation";
@@ -670,18 +778,19 @@ async function loadUserThreads(uid) {
670
  }
671
 
672
  function renderHistorySidebar() {
 
673
  if (!currentUser) {
674
  historyList.innerHTML = `<p class="text-zinc-500 px-2 py-2 text-[11px]">Sign in to sync history</p>`;
675
- clearAllBtn.classList.add('hidden');
676
  return;
677
  }
678
  if (cachedThreads.length === 0) {
679
  historyList.innerHTML = `<p class="text-zinc-500 px-2 py-2 text-[11px]">No conversations yet</p>`;
680
- clearAllBtn.classList.add('hidden');
681
  return;
682
  }
683
 
684
- clearAllBtn.classList.remove('hidden');
685
  historyList.innerHTML = '';
686
 
687
  cachedThreads.forEach((thread) => {
@@ -725,24 +834,28 @@ function openThread(thread) {
725
  if (isGenerating && abortController) abortController.abort();
726
  currentChatId = thread.id;
727
  currentChatMessages = Array.isArray(thread.messages) ? [...thread.messages] : [];
728
- chat.innerHTML = '';
729
- if (currentChatMessages.length === 0) chat.appendChild(createWelcomeScreen());
730
- else currentChatMessages.forEach(msg => appendMessage(msg.role, msg.text));
 
 
731
  renderHistorySidebar();
732
  toggleDrawer(false);
733
  }
734
 
735
- clearAllBtn.onclick = async () => {
736
- if (!currentUser || !db || !confirm("Clear all conversations?")) return;
737
- try {
738
- const snap = await db.collection("users").doc(currentUser.uid).collection("threads").get();
739
- await Promise.all(snap.docs.map(d => d.ref.delete()));
740
- startNewChat();
741
- await loadUserThreads(currentUser.uid);
742
- } catch (err) {
743
- console.error("Clear all failed:", err);
744
- }
745
- };
 
 
746
 
747
  // Firebase Auth & Firestore Initialization
748
  let auth = null;
@@ -756,11 +869,13 @@ try {
756
 
757
  auth.onAuthStateChanged(async (user) => {
758
  currentUser = user;
 
 
759
  if (user) {
760
- userDisplay.textContent = user.email.split('@')[0];
761
- syncStatus.textContent = "Synced to Cloud";
762
- authActionBtn.textContent = "Logout";
763
- upgradeBtn.classList.remove('hidden');
764
 
765
  try {
766
  const doc = await db.collection("users").doc(user.uid).get();
@@ -775,36 +890,79 @@ try {
775
  } else {
776
  currentPlan = data.plan || "Free";
777
  }
 
 
 
 
 
 
 
 
 
 
 
778
  } else {
779
  currentPlan = "Free";
 
780
  }
781
  } catch (e) {
782
  currentPlan = "Free";
 
783
  }
784
 
785
- userPlan.textContent = `Plan: ${currentPlan}`;
786
- if (currentPlan === "Pro") {
787
- userPlan.className = "text-[11px] font-semibold text-amber-400";
788
- upgradeBtn.textContent = "Pro Active";
789
- upgradeBtn.disabled = true;
790
- } else if (currentPlan === "Plus") {
791
- userPlan.className = "text-[11px] font-semibold text-emerald-400";
792
- upgradeBtn.textContent = "Plus Active";
793
- upgradeBtn.disabled = false;
794
- } else {
795
- userPlan.className = "text-[11px] font-semibold text-emerald-400";
796
- upgradeBtn.textContent = "Upgrade";
797
- upgradeBtn.disabled = false;
 
 
 
 
 
 
 
 
798
  }
799
 
 
 
800
  loadUserThreads(user.uid);
801
  } else {
802
- userDisplay.textContent = "Guest User";
803
- syncStatus.textContent = "Local Session";
804
- authActionBtn.textContent = "Login";
805
- userPlan.textContent = "Plan: Free";
806
- userPlan.className = "text-[11px] font-semibold text-emerald-400";
807
- upgradeBtn.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  cachedThreads = [];
809
  renderHistorySidebar();
810
  }
@@ -814,46 +972,62 @@ try {
814
  console.warn("Firebase restricted:", e);
815
  }
816
 
817
- authActionBtn.onclick = () => {
818
- if (currentUser && auth) {
819
- auth.signOut();
820
- } else {
821
- authError.classList.add('hidden');
822
- authModal.classList.remove('hidden');
823
- toggleDrawer(false);
824
- }
825
- };
826
- closeAuth.onclick = () => authModal.classList.add('hidden');
 
 
827
 
828
- authToggleMode.onclick = () => {
829
- isAuthRegister = !isAuthRegister;
830
- document.getElementById('auth-modal-title').textContent = isAuthRegister ? "Create Account" : "Account Login";
831
- authSubmitBtn.textContent = isAuthRegister ? "Register" : "Sign In";
832
- authToggleMode.textContent = isAuthRegister ? "Already have an account? Sign In" : "Need an account? Register";
833
- };
834
 
835
- authSubmitBtn.onclick = async () => {
836
- if (!auth) return;
837
- const email = authEmail.value.trim();
838
- const pass = authPassword.value.trim();
839
- authError.classList.add('hidden');
840
- if (!email || pass.length < 6) {
841
- authError.textContent = "Valid email & 6+ char password required.";
842
- authError.classList.remove('hidden');
843
- return;
844
- }
845
- try {
846
- if (isAuthRegister) {
847
- await auth.createUserWithEmailAndPassword(email, pass);
848
- } else {
849
- await auth.signInWithEmailAndPassword(email, pass);
 
 
 
 
 
 
 
850
  }
851
- authModal.classList.add('hidden');
852
- } catch (err) {
853
- authError.textContent = err.message.replace("Firebase: ", "");
854
- authError.classList.remove('hidden');
855
- }
856
- };
857
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
858
  updateScriptUI();
859
  updatePricingUI();
 
 
51
  const upgradeBtn = document.getElementById('upgrade-btn');
52
  const syncStatus = document.getElementById('sync-status');
53
 
54
+ // Model Switcher Elements
55
+ const modelSelectBtn = document.getElementById('model-select-btn');
56
+ const modelDropdown = document.getElementById('model-dropdown');
57
+ const currentModelDisplay = document.getElementById('current-model-display');
58
+ const selectOrki = document.getElementById('select-orki');
59
+ const selectOkafwr = document.getElementById('select-okafwr');
60
+ const okafwrLockIcon = document.getElementById('okafwr-lock-icon');
61
+
62
+ // Quota / Usage Elements
63
+ const usageCountText = document.getElementById('usage-count-text');
64
+ const usageProgressBar = document.getElementById('usage-progress-bar');
65
+
66
  // Upgrade Modal Elements
67
  const upgradeModal = document.getElementById('upgrade-modal');
68
  const closeUpgrade = document.getElementById('close-upgrade');
 
83
  const personaInput = document.getElementById('persona-input');
84
  const saveSettingsBtn = document.getElementById('save-settings');
85
 
86
+ // State Storage Helpers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  function safeGet(key, fallback) {
88
  try { return localStorage.getItem(key) || fallback; } catch (e) { return fallback; }
89
  }
 
91
  try { localStorage.setItem(key, val); } catch (e) {}
92
  }
93
 
94
+ function getTodayString() {
95
+ return new Date().toISOString().split('T')[0];
96
+ }
97
+
98
+ // App State
99
  let currentScript = safeGet('arki_script', 'deva');
100
  let userPersona = safeGet('arki_persona', '');
101
  let currentUser = null;
102
  let currentPlan = "Free";
103
+ let selectedModel = 'orki-3.0';
104
+ let currentDailyUsage = 0;
105
  let isAuthRegister = false;
106
  let abortController = null;
107
  let isGenerating = false;
108
  let currentChatId = null;
109
  let currentChatMessages = [];
110
  let selectedCycle = 'monthly';
111
+ let cachedThreads = [];
112
+
113
+ // Tier Limits & Pricing
114
+ const DAILY_LIMITS = {
115
+ Free: 10,
116
+ Plus: 100,
117
+ Pro: Infinity
118
+ };
119
 
 
120
  const PRICING = {
121
  weekly: { plus: 29, pro: 59, plusPaise: 2900, proPaise: 5900, label: '/wk' },
122
  monthly: { plus: 99, pro: 199, plusPaise: 9900, proPaise: 19900, label: '/mo' },
 
132
  return text.replace(/\n/g, '<br>');
133
  }
134
 
135
+ // UI State Management
136
  function setGeneratingState(generating) {
137
  isGenerating = generating;
138
+ if (!sendBtn) return;
139
  if (generating) {
140
  sendBtn.classList.remove('bg-emerald-600', 'hover:bg-emerald-500');
141
  sendBtn.classList.add('bg-red-600', 'hover:bg-red-500');
 
155
  }
156
  }
157
 
158
+ function updateUsageUI() {
159
+ if (!usageCountText || !usageProgressBar) return;
160
+ const limit = DAILY_LIMITS[currentPlan] || 10;
161
+
162
+ if (limit === Infinity) {
163
+ usageCountText.textContent = "Unlimited";
164
+ usageCountText.className = "text-[10px] font-bold text-amber-400";
165
+ usageProgressBar.style.width = "100%";
166
+ usageProgressBar.className = "h-full bg-amber-400 rounded-full";
167
+ return;
168
+ }
169
+
170
+ const pct = Math.min(100, Math.round((currentDailyUsage / limit) * 100));
171
+ usageCountText.textContent = `${currentDailyUsage} / ${limit}`;
172
+ usageCountText.className = pct >= 100 ? "text-[10px] font-bold text-red-400" : "text-[10px] font-medium text-zinc-400";
173
+ usageProgressBar.style.width = `${pct}%`;
174
+
175
+ if (pct >= 90) {
176
+ usageProgressBar.className = "h-full bg-red-500 rounded-full transition-all duration-300";
177
+ } else if (pct >= 70) {
178
+ usageProgressBar.className = "h-full bg-amber-500 rounded-full transition-all duration-300";
179
+ } else {
180
+ usageProgressBar.className = "h-full bg-emerald-500 rounded-full transition-all duration-300";
181
+ }
182
+ }
183
+
184
+ function updateModelLockUI() {
185
+ if (!okafwrLockIcon) return;
186
+ if (currentPlan === 'Pro') {
187
+ okafwrLockIcon.textContent = '✓';
188
+ okafwrLockIcon.className = 'text-amber-400 text-xs font-bold';
189
+ } else {
190
+ okafwrLockIcon.textContent = '🔒';
191
+ okafwrLockIcon.className = 'text-zinc-500 text-xs';
192
+ }
193
+ }
194
+
195
+ // Drawer Controls
196
  function toggleDrawer(open) {
197
+ if (!drawer || !drawerBackdrop) return;
198
  drawer.style.transform = open ? 'translateX(0%)' : 'translateX(-100%)';
199
  drawerBackdrop.classList.toggle('hidden', !open);
200
  }
201
 
202
+ if (menuBtn) menuBtn.onclick = () => toggleDrawer(true);
203
+ if (closeDrawer) closeDrawer.onclick = () => toggleDrawer(false);
204
+ if (drawerBackdrop) drawerBackdrop.onclick = () => toggleDrawer(false);
205
 
206
  function createWelcomeScreen() {
207
  const div = document.createElement('div');
 
218
  if (isGenerating && abortController) abortController.abort();
219
  currentChatId = null;
220
  currentChatMessages = [];
221
+ if (chat) {
222
+ chat.innerHTML = '';
223
+ chat.appendChild(createWelcomeScreen());
224
+ }
225
  toggleDrawer(false);
226
  renderHistorySidebar();
227
  }
228
 
229
+ if (newChatBtn) newChatBtn.onclick = startNewChat;
230
+
231
+ // Model Switcher Logic
232
+ if (modelSelectBtn && modelDropdown) {
233
+ modelSelectBtn.onclick = (e) => {
234
+ e.stopPropagation();
235
+ modelDropdown.classList.toggle('hidden');
236
+ };
237
+ }
238
 
239
+ document.addEventListener('click', () => {
240
+ if (modelDropdown && !modelDropdown.classList.contains('hidden')) {
241
+ modelDropdown.classList.add('hidden');
242
+ }
243
+ });
244
+
245
+ if (selectOrki) {
246
+ selectOrki.onclick = () => {
247
+ selectedModel = 'orki-3.0';
248
+ if (currentModelDisplay) {
249
+ currentModelDisplay.textContent = 'Orki 3.0';
250
+ currentModelDisplay.className = 'text-zinc-200';
251
+ }
252
+ if (modelDropdown) modelDropdown.classList.add('hidden');
253
+ };
254
+ }
255
+
256
+ if (selectOkafwr) {
257
+ selectOkafwr.onclick = () => {
258
+ if (currentPlan !== 'Pro') {
259
+ if (modelDropdown) modelDropdown.classList.add('hidden');
260
+ if (upgradeModal) upgradeModal.classList.remove('hidden');
261
+ alert("Okafwr 2.1 requires an active Pro subscription.");
262
+ return;
263
+ }
264
+ selectedModel = 'okafwr-2.1';
265
+ if (currentModelDisplay) {
266
+ currentModelDisplay.textContent = 'Okafwr 2.1 ⚡';
267
+ currentModelDisplay.className = 'text-amber-400 font-bold';
268
+ }
269
+ if (modelDropdown) modelDropdown.classList.add('hidden');
270
+ };
271
+ }
272
+
273
+ // Textarea Auto-expand & Enter Key Handling
274
+ if (input) {
275
+ input.addEventListener('input', function () {
276
+ this.style.height = 'auto';
277
+ this.style.height = Math.min(this.scrollHeight, 112) + 'px';
278
+ });
279
+
280
+ input.addEventListener('keydown', function (e) {
281
+ if (e.key === 'Enter' && !e.shiftKey) {
282
+ e.preventDefault();
283
+ handleSend();
284
+ }
285
+ });
286
+ }
287
+
288
+ // Pricing Selector UI
289
  function updatePricingUI() {
290
  const p = PRICING[selectedCycle];
291
  if (plusPriceText) plusPriceText.innerHTML = `₹${p.plus}<span class="text-xs text-zinc-500 font-normal">${p.label}</span>`;
 
303
  if (cycleMonthly) cycleMonthly.onclick = () => { selectedCycle = 'monthly'; updatePricingUI(); };
304
  if (cycleYearly) cycleYearly.onclick = () => { selectedCycle = 'yearly'; updatePricingUI(); };
305
 
306
+ if (upgradeBtn) {
307
+ upgradeBtn.onclick = () => {
308
+ if (!currentUser) return;
309
+ if (upgradeModal) upgradeModal.classList.remove('hidden');
310
+ toggleDrawer(false);
311
+ };
312
+ }
313
 
314
  if (closeUpgrade) closeUpgrade.onclick = () => upgradeModal.classList.add('hidden');
315
 
 
322
  currency: "INR",
323
  name: "Orki AI",
324
  description: `${planTier} Plan (${selectedCycle})`,
325
+ prefill: { email: currentUser ? currentUser.email : "", contact: "9999999999" },
326
  theme: { color: planTier === "Pro" ? "#f59e0b" : "#059669" },
327
  handler: async function (res) {
328
  try {
 
345
  userPlan.className = planTier === "Pro" ? "text-[11px] font-semibold text-amber-400" : "text-[11px] font-semibold text-emerald-400";
346
  upgradeBtn.textContent = `${planTier} Active`;
347
  upgradeBtn.disabled = true;
348
+ updateModelLockUI();
349
+ updateUsageUI();
350
  alert(`Success! You are now subscribed to ${planTier} until ${expiresAt.toLocaleDateString()}.`);
351
  } catch (err) {
352
  alert("Activation failed: " + err.message);
 
362
  if (buyProBtn) buyProBtn.onclick = () => initiatePayment("Pro", PRICING[selectedCycle].proPaise);
363
 
364
  // Settings Controls
365
+ if (openSettingsBtn) {
366
+ openSettingsBtn.onclick = () => {
367
+ toggleDrawer(false);
368
+ if (personaInput) personaInput.value = userPersona;
369
+ updateScriptUI();
370
+ if (settingsModal) settingsModal.classList.remove('hidden');
371
+ };
372
+ }
373
+
374
+ if (closeSettingsBtn) closeSettingsBtn.onclick = () => settingsModal.classList.add('hidden');
375
 
376
  function updateScriptUI() {
377
+ if (!optDeva || !optRom) return;
378
  if (currentScript === 'deva') {
379
  optDeva.className = "py-2 rounded-lg text-xs font-semibold bg-emerald-600 text-white transition cursor-pointer";
380
  optRom.className = "py-2 rounded-lg text-xs font-medium text-zinc-400 hover:text-white transition cursor-pointer";
 
384
  }
385
  }
386
 
387
+ if (optDeva) optDeva.onclick = () => { currentScript = 'deva'; updateScriptUI(); };
388
+ if (optRom) optRom.onclick = () => { currentScript = 'roman'; updateScriptUI(); };
389
 
390
+ if (saveSettingsBtn) {
391
+ saveSettingsBtn.onclick = () => {
392
+ userPersona = personaInput ? personaInput.value.trim() : '';
393
+ safeSet('arki_script', currentScript);
394
+ safeSet('arki_persona', userPersona);
395
+ if (settingsModal) settingsModal.classList.add('hidden');
396
+ };
397
+ }
398
 
399
+ // Chat Bubbles (Forced Text Wrapping)
400
  function createBubbleElement(role) {
401
  const wrapper = document.createElement('div');
402
  wrapper.className = `flex ${role === 'user' ? 'justify-end' : 'justify-start'}`;
 
405
  ? 'bg-emerald-600 text-white rounded-2xl rounded-tr-xs px-4 py-2.5 max-w-[85%] text-[15px] whitespace-pre-wrap leading-relaxed shadow-sm break-words [overflow-wrap:anywhere]'
406
  : 'bg-[#242427] border border-zinc-700/40 text-zinc-100 rounded-2xl rounded-tl-xs px-4 py-2.5 max-w-[85%] text-[15px] shadow-sm leading-relaxed prose-custom break-words [overflow-wrap:anywhere]';
407
  wrapper.appendChild(bubble);
408
+ if (chat) {
409
+ chat.appendChild(wrapper);
410
+ chat.scrollTop = chat.scrollHeight;
411
+ }
412
  return bubble;
413
  }
414
 
 
415
  function appendMessage(role, text) {
416
  const bubble = createBubbleElement(role);
417
  bubble.innerHTML = role === 'user' ? text : renderMarkdown(text);
418
+ if (chat) chat.scrollTop = chat.scrollHeight;
419
  }
420
 
421
  // Audio Recording
 
423
  let audioChunks = [];
424
  let isRecording = false;
425
 
426
+ if (micBtn) {
427
+ micBtn.onclick = () => {
428
+ if (isRecording) stopRecording();
429
+ else startRecording();
430
+ };
431
+ }
432
 
433
  async function startRecording() {
434
  if (isGenerating) return;
 
461
  function stopRecording() {
462
  if (mediaRecorder && mediaRecorder.state !== 'inactive') mediaRecorder.stop();
463
  isRecording = false;
464
+ if (micBtn) {
465
+ micBtn.classList.remove('bg-red-600', 'text-white', 'animate-pulse');
466
+ micBtn.classList.add('bg-zinc-800', 'text-zinc-300');
467
+ }
468
  }
469
 
470
  function blobToBase64(blob) {
 
476
  });
477
  }
478
 
479
+ async function persistUsageIncrement() {
480
+ currentDailyUsage++;
481
+ updateUsageUI();
482
+ const today = getTodayString();
483
+ if (currentUser && db) {
484
+ try {
485
+ await db.collection("users").doc(currentUser.uid).set({
486
+ dailyUsage: { date: today, count: currentDailyUsage }
487
+ }, { merge: true });
488
+ } catch (e) {
489
+ console.warn("Usage sync error:", e);
490
+ }
491
+ } else {
492
+ safeSet('arki_guest_usage', JSON.stringify({ date: today, count: currentDailyUsage }));
493
+ }
494
+ }
495
+
496
  async function handleAudioSend(audioBlob, mimeType) {
497
  if (isGenerating) return;
498
+
499
+ const limit = DAILY_LIMITS[currentPlan] || 10;
500
+ if (currentDailyUsage >= limit) {
501
+ alert(`You reached your daily limit of ${limit} messages. Upgrade to get higher limits.`);
502
+ if (upgradeModal) upgradeModal.classList.remove('hidden');
503
+ return;
504
+ }
505
+
506
  const welcome = document.getElementById('welcome-screen');
507
  if (welcome) welcome.remove();
508
 
 
510
  userBubble.textContent = '🎙️ Transcribing...';
511
  currentChatMessages.push({ role: 'user', text: '[Voice Note]' });
512
  setGeneratingState(true);
513
+ if (thinking) thinking.classList.remove('hidden');
514
 
515
  const base64Audio = await blobToBase64(audioBlob);
516
  const scriptConstraint = currentScript === 'roman'
 
540
  signal: abortController.signal,
541
  body: JSON.stringify({
542
  plan: currentPlan,
543
+ model: selectedModel,
544
  script: currentScript,
545
  userPersona: userPersona,
546
  contents: apiHistory,
547
+ generationConfig: { temperature: 0.4, maxOutputTokens: selectedModel === 'okafwr-2.1' ? 4000 : 1500 }
548
  })
549
  });
550
 
551
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
552
+ if (thinking) thinking.classList.add('hidden');
553
  const aiBubble = createBubbleElement('model');
554
  const reader = res.body.getReader();
555
  const decoder = new TextDecoder('utf-8');
 
584
  } else {
585
  aiBubble.innerHTML = renderMarkdown(fullAiText);
586
  }
587
+ if (chat) chat.scrollTop = chat.scrollHeight;
588
  }
589
  } catch (e) {}
590
  }
 
596
  currentChatMessages[currentChatMessages.length - 1].text = parts[0].replace('TRANSCRIPTION:', '').trim() || '[Voice Note]';
597
  currentChatMessages.push({ role: 'model', text: parts.slice(1).join('RESPONSE:').trim() });
598
  await saveCurrentThreadToCloud();
599
+ await persistUsageIncrement();
600
  } else if (fullAiText.trim()) {
601
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
602
  await saveCurrentThreadToCloud();
603
+ await persistUsageIncrement();
604
  }
605
  } catch (err) {
606
  if (err.name !== 'AbortError') {
607
+ if (thinking) thinking.classList.add('hidden');
608
  userBubble.textContent = '🎙️ [Failed Voice Note]';
609
  appendMessage('model', `Voice processing error: ${err.message}`);
610
  }
611
  } finally {
612
+ if (thinking) thinking.classList.add('hidden');
613
  setGeneratingState(false);
614
  abortController = null;
615
  }
 
617
 
618
  // Text Send
619
  async function handleSend() {
 
620
  if (isGenerating) {
621
  if (abortController) abortController.abort();
622
  return;
623
  }
624
 
625
+ const limit = DAILY_LIMITS[currentPlan] || 10;
626
+ if (currentDailyUsage >= limit) {
627
+ alert(`You reached your daily limit of ${limit} messages. Upgrade to get higher limits.`);
628
+ if (upgradeModal) upgradeModal.classList.remove('hidden');
629
+ return;
630
+ }
631
+
632
+ const userText = input ? input.value.trim() : '';
633
  if (!userText) return;
634
 
635
  const welcome = document.getElementById('welcome-screen');
636
  if (welcome) welcome.remove();
637
+
 
638
  appendMessage('user', userText);
639
  currentChatMessages.push({ role: 'user', text: userText });
640
+
641
  input.value = '';
642
+ input.style.height = 'auto'; // Reset auto-expanded height
643
+
644
  setGeneratingState(true);
645
+ if (thinking) thinking.classList.remove('hidden');
646
 
647
  const scriptConstraint = currentScript === 'roman'
648
  ? "\n[Constraint: Reply ONLY in Romanized Bodo ending in 'i' not 'y'. No Devanagari.]"
 
663
  signal: abortController.signal,
664
  body: JSON.stringify({
665
  plan: currentPlan,
 
666
  model: selectedModel,
667
+ script: currentScript,
668
  userPersona: userPersona,
669
  contents: apiHistory,
670
+ generationConfig: { temperature: 0.5, maxOutputTokens: selectedModel === 'okafwr-2.1' ? 4000 : 1500 }
671
  })
672
  });
673
 
674
  if (!res.ok) {
675
  const errData = await res.json().catch(() => ({}));
676
+ throw new Error(errData.error || `HTTP ${res.status}`);
677
  }
678
 
679
+ if (thinking) thinking.classList.add('hidden');
680
  const aiBubble = createBubbleElement('model');
681
  const reader = res.body.getReader();
682
  const decoder = new TextDecoder('utf-8');
 
700
  if (chunk) {
701
  fullAiText += chunk;
702
  aiBubble.innerHTML = renderMarkdown(fullAiText);
703
+ if (chat) chat.scrollTop = chat.scrollHeight;
704
  }
705
  } catch (e) {}
706
  }
 
710
  if (fullAiText.trim()) {
711
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
712
  await saveCurrentThreadToCloud();
713
+ await persistUsageIncrement();
714
  }
715
  } catch (err) {
716
  if (err.name === 'AbortError') {
717
  if (fullAiText.trim()) {
718
  currentChatMessages.push({ role: 'model', text: fullAiText.trim() });
719
  await saveCurrentThreadToCloud();
720
+ await persistUsageIncrement();
721
  }
722
  } else {
723
+ if (thinking) thinking.classList.add('hidden');
724
  appendMessage('model', err.message.includes('429') ? 'Rate limit hit (5 req/min). 15s नेनानै फिन लिर।' : `Error: ${err.message}`);
725
  }
726
  } finally {
727
+ if (thinking) thinking.classList.add('hidden');
728
  setGeneratingState(false);
729
  abortController = null;
730
+ if (input) input.focus();
731
  }
732
  }
733
 
734
+ if (form) {
735
+ form.onsubmit = (e) => {
736
+ e.preventDefault();
737
+ handleSend();
738
+ };
739
+ }
740
 
741
  // Cloud Firestore Threads
 
 
742
  async function saveCurrentThreadToCloud() {
743
  if (!currentUser || !db || currentChatMessages.length === 0) return;
744
  const firstPrompt = currentChatMessages.find(m => m.role === 'user')?.text || "New Conversation";
 
778
  }
779
 
780
  function renderHistorySidebar() {
781
+ if (!historyList) return;
782
  if (!currentUser) {
783
  historyList.innerHTML = `<p class="text-zinc-500 px-2 py-2 text-[11px]">Sign in to sync history</p>`;
784
+ if (clearAllBtn) clearAllBtn.classList.add('hidden');
785
  return;
786
  }
787
  if (cachedThreads.length === 0) {
788
  historyList.innerHTML = `<p class="text-zinc-500 px-2 py-2 text-[11px]">No conversations yet</p>`;
789
+ if (clearAllBtn) clearAllBtn.classList.add('hidden');
790
  return;
791
  }
792
 
793
+ if (clearAllBtn) clearAllBtn.classList.remove('hidden');
794
  historyList.innerHTML = '';
795
 
796
  cachedThreads.forEach((thread) => {
 
834
  if (isGenerating && abortController) abortController.abort();
835
  currentChatId = thread.id;
836
  currentChatMessages = Array.isArray(thread.messages) ? [...thread.messages] : [];
837
+ if (chat) {
838
+ chat.innerHTML = '';
839
+ if (currentChatMessages.length === 0) chat.appendChild(createWelcomeScreen());
840
+ else currentChatMessages.forEach(msg => appendMessage(msg.role, msg.text));
841
+ }
842
  renderHistorySidebar();
843
  toggleDrawer(false);
844
  }
845
 
846
+ if (clearAllBtn) {
847
+ clearAllBtn.onclick = async () => {
848
+ if (!currentUser || !db || !confirm("Clear all conversations?")) return;
849
+ try {
850
+ const snap = await db.collection("users").doc(currentUser.uid).collection("threads").get();
851
+ await Promise.all(snap.docs.map(d => d.ref.delete()));
852
+ startNewChat();
853
+ await loadUserThreads(currentUser.uid);
854
+ } catch (err) {
855
+ console.error("Clear all failed:", err);
856
+ }
857
+ };
858
+ }
859
 
860
  // Firebase Auth & Firestore Initialization
861
  let auth = null;
 
869
 
870
  auth.onAuthStateChanged(async (user) => {
871
  currentUser = user;
872
+ const today = getTodayString();
873
+
874
  if (user) {
875
+ if (userDisplay) userDisplay.textContent = user.email.split('@')[0];
876
+ if (syncStatus) syncStatus.textContent = "Synced to Cloud";
877
+ if (authActionBtn) authActionBtn.textContent = "Logout";
878
+ if (upgradeBtn) upgradeBtn.classList.remove('hidden');
879
 
880
  try {
881
  const doc = await db.collection("users").doc(user.uid).get();
 
890
  } else {
891
  currentPlan = data.plan || "Free";
892
  }
893
+
894
+ // Sync daily usage count
895
+ const usageData = data.dailyUsage || {};
896
+ if (usageData.date !== today) {
897
+ currentDailyUsage = 0;
898
+ await db.collection("users").doc(user.uid).set({
899
+ dailyUsage: { date: today, count: 0 }
900
+ }, { merge: true });
901
+ } else {
902
+ currentDailyUsage = usageData.count || 0;
903
+ }
904
  } else {
905
  currentPlan = "Free";
906
+ currentDailyUsage = 0;
907
  }
908
  } catch (e) {
909
  currentPlan = "Free";
910
+ currentDailyUsage = 0;
911
  }
912
 
913
+ if (userPlan) {
914
+ userPlan.textContent = `Plan: ${currentPlan}`;
915
+ if (currentPlan === "Pro") {
916
+ userPlan.className = "text-[11px] font-semibold text-amber-400";
917
+ if (upgradeBtn) {
918
+ upgradeBtn.textContent = "Pro Active";
919
+ upgradeBtn.disabled = true;
920
+ }
921
+ } else if (currentPlan === "Plus") {
922
+ userPlan.className = "text-[11px] font-semibold text-emerald-400";
923
+ if (upgradeBtn) {
924
+ upgradeBtn.textContent = "Plus Active";
925
+ upgradeBtn.disabled = false;
926
+ }
927
+ } else {
928
+ userPlan.className = "text-[11px] font-semibold text-emerald-400";
929
+ if (upgradeBtn) {
930
+ upgradeBtn.textContent = "Upgrade";
931
+ upgradeBtn.disabled = false;
932
+ }
933
+ }
934
  }
935
 
936
+ updateModelLockUI();
937
+ updateUsageUI();
938
  loadUserThreads(user.uid);
939
  } else {
940
+ if (userDisplay) userDisplay.textContent = "Guest User";
941
+ if (syncStatus) syncStatus.textContent = "Local Session";
942
+ if (authActionBtn) authActionBtn.textContent = "Login";
943
+ if (userPlan) {
944
+ userPlan.textContent = "Plan: Free";
945
+ userPlan.className = "text-[11px] font-semibold text-emerald-400";
946
+ }
947
+ if (upgradeBtn) upgradeBtn.classList.add('hidden');
948
+
949
+ // Restore guest usage count
950
+ try {
951
+ const guestUsage = JSON.parse(safeGet('arki_guest_usage', 'null')) || {};
952
+ currentDailyUsage = (guestUsage.date === today) ? (guestUsage.count || 0) : 0;
953
+ } catch (e) {
954
+ currentDailyUsage = 0;
955
+ }
956
+
957
+ currentPlan = "Free";
958
+ selectedModel = 'orki-3.0';
959
+ if (currentModelDisplay) {
960
+ currentModelDisplay.textContent = 'Orki 3.0';
961
+ currentModelDisplay.className = 'text-zinc-200';
962
+ }
963
+
964
+ updateModelLockUI();
965
+ updateUsageUI();
966
  cachedThreads = [];
967
  renderHistorySidebar();
968
  }
 
972
  console.warn("Firebase restricted:", e);
973
  }
974
 
975
+ // Auth Actions
976
+ if (authActionBtn) {
977
+ authActionBtn.onclick = () => {
978
+ if (currentUser && auth) {
979
+ auth.signOut();
980
+ } else {
981
+ if (authError) authError.classList.add('hidden');
982
+ if (authModal) authModal.classList.remove('hidden');
983
+ toggleDrawer(false);
984
+ }
985
+ };
986
+ }
987
 
988
+ if (closeAuth) closeAuth.onclick = () => authModal.classList.add('hidden');
 
 
 
 
 
989
 
990
+ if (authToggleMode) {
991
+ authToggleMode.onclick = () => {
992
+ isAuthRegister = !isAuthRegister;
993
+ document.getElementById('auth-modal-title').textContent = isAuthRegister ? "Create Account" : "Account Login";
994
+ authSubmitBtn.textContent = isAuthRegister ? "Register" : "Sign In";
995
+ authToggleMode.textContent = isAuthRegister ? "Already have an account? Sign In" : "Need an account? Register";
996
+ };
997
+ }
998
+
999
+ if (authSubmitBtn) {
1000
+ authSubmitBtn.onclick = async () => {
1001
+ if (!auth) return;
1002
+ const email = authEmail.value.trim();
1003
+ const pass = authPassword.value.trim();
1004
+ if (authError) authError.classList.add('hidden');
1005
+
1006
+ if (!email || pass.length < 6) {
1007
+ if (authError) {
1008
+ authError.textContent = "Valid email & 6+ char password required.";
1009
+ authError.classList.remove('hidden');
1010
+ }
1011
+ return;
1012
  }
 
 
 
 
 
 
1013
 
1014
+ try {
1015
+ if (isAuthRegister) {
1016
+ await auth.createUserWithEmailAndPassword(email, pass);
1017
+ } else {
1018
+ await auth.signInWithEmailAndPassword(email, pass);
1019
+ }
1020
+ if (authModal) authModal.classList.add('hidden');
1021
+ } catch (err) {
1022
+ if (authError) {
1023
+ authError.textContent = err.message.replace("Firebase: ", "");
1024
+ authError.classList.remove('hidden');
1025
+ }
1026
+ }
1027
+ };
1028
+ }
1029
+
1030
+ // Initial UI Calls
1031
  updateScriptUI();
1032
  updatePricingUI();
1033
+ updateUsageUI();