dexter2389 commited on
Commit
e5ab376
·
1 Parent(s): 8b0dded

Add support for analytics & do minor UI changes

Browse files
Files changed (3) hide show
  1. app/static/index.html +245 -156
  2. app/static/login.html +94 -77
  3. uv.lock +1 -1
app/static/index.html CHANGED
@@ -2,30 +2,34 @@
2
  <html lang="en" class="light">
3
 
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
7
  <title>AI Chat Interface</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script>
10
  tailwind.config = {
11
- darkMode: 'class',
12
  theme: {
13
  extend: {
14
  colors: {
15
  primary: {
16
- DEFAULT: '#3b82f6',
17
- foreground: '#ffffff',
18
  },
19
  secondary: {
20
- DEFAULT: '#f3f4f6',
21
- foreground: '#1f2937',
22
  },
23
- }
24
- }
25
- }
26
- }
27
  </script>
28
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
 
 
29
  </head>
30
 
31
  <body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-['Inter'] transition-colors duration-200">
@@ -34,43 +38,46 @@
34
  <header class="border-b border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800">
35
  <div class="max-w-screen-xl mx-auto flex items-center justify-between">
36
  <div class="flex items-center">
37
- <h1 class="text-xl font-medium">OpenGPT with <svg
38
- style='display: inline-block; vertical-align: -16.75%; margin-right: -1.5%;'
39
- stroke_linecap='round' stroke_linejoin='round' width='32' height='32' viewBox='0 0 24 24'
40
- xmlns='http://www.w3.org/2000/svg'>
 
 
 
 
41
  <defs>
42
- <linearGradient id='logo-grad-arm' x1='0%' y1='0%' x2='100%' y2='0%'>
43
- <stop offset='0%' style='stop-color:#f7cd1b; stop-opacity:1' />
44
- <stop offset='100%' style='stop-color:#f9800c; stop-opacity:1' />
45
  </linearGradient>
46
  </defs>
47
- <g class='tooltip' style='display:inline'>
48
- <path fill='#067ff3'
49
- d='M.227 20.705a5.444 5.444 0 0 0 4.745-2.858l4.48-8.13L7.67 6.613.03 20.368a.227.227 0 0 0 .198.337z' />
50
- <path fill='#07b682'
51
- d='M16.003 13.074l-2.747 1.361 1.944 3.39a5.697 5.682-.012 0 0 4.935 2.869.19.19 0 0 0 .165-.286z' />
52
- <path fill='url(#logo-grad-arm)'
53
- d='M7.99 14.555L6.2 17.872a.03.03 0 0 0 .04.042l17.744-8.798a.03.03 0 0 0-.022-.055l-11.67 3.765-3.851 1.344a.819.819 0 0 0-.451.385z' />
54
- <path class='logo-arm'
55
- d='M10.011 3.3a.683.681-.012 0 0-.733.339L8.19 5.603l4.137 7.212 2.964-.956-4.825-8.234a.683.681-.012 0 0-.455-.324z' />
56
  </g>
57
  <style>
58
  @media (prefers-color-scheme: light) {
59
  .logo-arm {
60
-
61
- fill: black
62
  }
63
  }
64
 
65
  @media (prefers-color-scheme: dark) {
66
  .logo-arm {
67
- fill: white
68
  }
69
  }
70
  </style>
71
- </svg><span class="font-semibold" style="color: #067ff3;">rc</span><span class="font-semibold"
72
- style="color: #f9800c;">a</span><span class="font-semibold"
73
- style="color: #07b682;">na</span></h1>
74
  </div>
75
  <!-- -->
76
 
@@ -84,7 +91,9 @@
84
  <a href="https://arcana.ad/"
85
  class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white font-medium">Home</a>
86
  <button id="logout-button"
87
- class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white font-medium">Logout</button>
 
 
88
  </nav>
89
  </div>
90
  </header>
@@ -107,32 +116,38 @@
107
  <ul class="py-1">
108
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
109
  data-value="gemma2">
110
- Gemma2</li>
 
111
  </ul>
112
  <ul class="py-1">
113
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
114
  data-value="llama3_1">
115
- Llama3.1</li>
 
116
  </ul>
117
  <ul class="py-1">
118
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
119
  data-value="llama3_3">
120
- Llama3.3</li>
 
121
  </ul>
122
  <ul class="py-1">
123
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
124
  data-value="qwen2_5">
125
- Qwen2.5</li>
 
126
  </ul>
127
  <ul class="py-1">
128
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
129
  data-value="deepseek_r1">
130
- Deepseek_R1</li>
 
131
  </ul>
132
  <ul class="py-1">
133
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
134
  data-value="gemma3">
135
- Gemma3</li>
 
136
  </ul>
137
  </div>
138
  </div>
@@ -180,27 +195,35 @@
180
  <button
181
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
182
  <div class="font-medium">Create a short 3 scene video script</div>
183
- <div class="text-gray-500 text-sm">set in a cyberpunk world run by AI</div>
 
 
184
  </button>
185
  <button
186
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
187
  <div class="font-medium">Write a python code</div>
188
- <div class="text-gray-500 text-sm">for a simple, functional web app</div>
 
 
189
  </button>
190
  <button
191
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
192
  <div class="font-medium">Help me study</div>
193
- <div class="text-gray-500 text-sm">vocabulary for a college entrance exam</div>
 
 
194
  </button>
195
  <button
196
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
197
  <div class="font-medium">Give me ideas</div>
198
- <div class="text-gray-500 text-sm">for a LinkedIn post for my recent promotion</div>
 
 
199
  </button>
200
  </div>
201
 
202
  <!-- Input area -->
203
- <div class="border-t border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800">
204
  <div class="max-w-screen-md mx-auto">
205
  <form id="chat-form" class="relative">
206
  <div
@@ -216,7 +239,7 @@
216
  </button>
217
  <input type="text" id="user-input"
218
  class="flex-1 py-3 px-2 bg-transparent focus:outline-none text-gray-800 dark:text-gray-200"
219
- placeholder="Send a Message" autocomplete="off">
220
  <button type="button"
221
  class="p-3 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300">
222
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
@@ -238,16 +261,14 @@
238
  </button>
239
  </div>
240
  </form>
241
-
242
- <!-- Footer text -->
243
- <div class="text-center text-gray-400 dark:text-gray-500 text-sm mt-3">
244
- LLMs can make mistakes. Verify important information.
245
- </div>
246
  </div>
247
  </div>
248
 
249
  <footer class="text-center p-4 text-gray-500 dark:text-gray-400 dark:bg-gray-800 text-sm">
250
- <p 2025 Arcana Inc. All rights reserved.</p>
 
 
 
251
  </footer>
252
  </main>
253
 
@@ -308,15 +329,15 @@
308
 
309
  .message-content em {
310
  font-style: normal;
311
- color: #4B5563;
312
  }
313
 
314
  .dark .message-content em {
315
- color: #9CA3AF;
316
  }
317
 
318
  .message-content code {
319
- font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
320
  background-color: #f1f5f9;
321
  padding: 0.1em 0.3em;
322
  border-radius: 0.25rem;
@@ -343,7 +364,6 @@
343
  border-color: #334155;
344
  }
345
 
346
-
347
  .message-content pre code {
348
  background-color: transparent;
349
  padding: 0;
@@ -378,16 +398,18 @@
378
  </style>
379
 
380
  <script>
381
- document.addEventListener('DOMContentLoaded', () => {
382
  const html = document.documentElement;
383
 
384
  // Function to set theme based on system preference
385
  function setThemeBasedOnSystemPreference() {
386
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
 
 
387
  if (prefersDark) {
388
- html.classList.add('dark');
389
  } else {
390
- html.classList.remove('dark');
391
  }
392
  }
393
 
@@ -395,61 +417,84 @@
395
  setThemeBasedOnSystemPreference();
396
 
397
  // Listen for system preference changes
398
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
399
- setThemeBasedOnSystemPreference();
400
- });
 
 
401
 
402
  // Check if user is logged in
403
- const verifiedId = sessionStorage.getItem('verifiedId');
404
  if (!verifiedId) {
405
- window.location.href = '/login';
406
  return;
407
  }
408
 
409
  // Logout functionality
410
- const logoutButton = document.getElementById('logout-button');
411
- logoutButton.addEventListener('click', () => {
412
- sessionStorage.removeItem('verifiedId');
413
- window.location.href = '/login';
414
  });
415
 
416
- const chatForm = document.getElementById('chat-form');
417
- const userInput = document.getElementById('user-input');
418
- const chatContainer = document.getElementById('chat-container');
419
- const loadingIndicator = document.getElementById('loading');
420
- const suggestionButtons = document.querySelectorAll('.max-w-screen-md.mx-auto.px-4.md\\:px-0.mb-6 button');
 
 
421
 
422
  // Model selector elements
423
- const modelDropdownButton = document.getElementById('model-dropdown-button');
424
- const modelDropdown = document.getElementById('model-dropdown');
425
- const modelOptions = document.querySelectorAll('.model-option');
426
- const selectedModelText = document.getElementById('selected-model-text');
 
 
 
 
427
 
428
  // Default model
429
- let selectedModel = 'gemma2';
430
 
431
  // Toggle dropdown
432
- modelDropdownButton.addEventListener('click', () => {
433
- modelDropdown.classList.toggle('hidden');
434
  });
435
 
436
  // Close dropdown when clicking outside
437
- document.addEventListener('click', (e) => {
438
- if (!modelDropdownButton.contains(e.target) && !modelDropdown.contains(e.target)) {
439
- modelDropdown.classList.add('hidden');
 
 
 
440
  }
441
  });
442
 
443
  // Handle model selection
444
- modelOptions.forEach(option => {
445
- option.addEventListener('click', () => {
446
- selectedModel = option.getAttribute('data-value');
447
  selectedModelText.textContent = option.textContent;
448
- modelDropdown.classList.add('hidden');
449
 
450
  // Add visual indication of selected model
451
- modelOptions.forEach(opt => opt.classList.remove('bg-blue-50', 'text-blue-700', 'dark:bg-blue-900/50', 'dark:text-blue-300'));
452
- option.classList.add('bg-blue-50', 'text-blue-700', 'dark:bg-blue-900/50', 'dark:text-blue-300');
 
 
 
 
 
 
 
 
 
 
 
 
453
  });
454
  });
455
 
@@ -458,57 +503,81 @@
458
 
459
  // Function to add a message to the chat
460
  function formatMessage(text) {
 
461
  // First, preserve code blocks by replacing them with placeholders
462
  const codeBlocks = [];
463
- text = text.replace(/\`\`\`([\s\S]*?)\`\`\`/g, function (match, code) {
464
- const id = codeBlocks.length;
465
- codeBlocks.push(code.trim());
466
- return `__CODE_BLOCK_${id}__`;
467
- });
 
 
 
468
 
469
  // Handle inline code
470
  const inlineCodes = [];
471
- text = text = text.replace(/`([^`]+)`/g, function (match, code) {
472
  const id = inlineCodes.length;
473
  inlineCodes.push(code);
474
  return `__INLINE_CODE_${id}__`;
475
  });
476
 
477
  // Replace ** with proper styling
478
- text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
479
 
480
  // Handle numbered lists
481
- text = text.replace(/(\d+\.\s[^*]*?)(?=\d+\.|$)/g, '<div class="mb-3">$1</div>');
 
 
 
482
 
483
  // Handle sections with asterisks
484
- text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
485
 
486
  // Handle unordered lists
487
- text = text.replace(/^- (.*?)$/gm, '<li>$1</li>');
488
- text = text.replace(/<li>.*?<\/li>(?:\s*<li>.*?<\/li>)+/gs, function (match) {
489
- return '<ul>' + match + '</ul>';
490
- });
 
 
 
491
 
492
  // Handle ordered lists
493
- text = text.replace(/^(\d+)\. (.*?)$/gm, '<li>$2</li>');
494
- text = text.replace(/<li>.*?<\/li>(?:\s*<li>.*?<\/li>)+/gs, function (match) {
495
- return '<ol>' + match + '</ol>';
496
- });
 
 
 
497
 
498
  // Handle blockquotes
499
- text = text.replace(/^> (.*?)$/gm, '<blockquote>$1</blockquote>');
500
 
501
  // Add proper spacing between sections
502
- text = text.split('\n').map(line => line.trim()).filter(line => line).join('</p><p>');
 
 
 
 
503
 
504
  // Restore code blocks
505
- codeBlocks.forEach((code, i) => {
506
- text = text.replace(`__CODE_BLOCK_${i}__`, `</p><pre><code>${escapeHtml(code)}</code></pre><p>`);
 
 
 
 
507
  });
508
 
509
  // Restore inline code
510
  inlineCodes.forEach((code, i) => {
511
- text = text.replace(`__INLINE_CODE_${i}__`, `<code>${escapeHtml(code)}</code>`);
 
 
 
512
  });
513
 
514
  return `<p>${text}</p>`;
@@ -516,6 +585,7 @@
516
 
517
  // Helper function to escape HTML in code blocks
518
  function escapeHtml(text) {
 
519
  return text
520
  .replace(/&/g, "&amp;")
521
  .replace(/</g, "&lt;")
@@ -527,39 +597,55 @@
527
  // Function to add a message to the chat
528
  function addMessage(content, isUser = false) {
529
  // Hide the welcome message when chat starts
530
- const welcomeMessage = chatContainer.querySelector('.flex.flex-col.items-center.justify-center');
 
 
531
  if (welcomeMessage) {
532
  welcomeMessage.remove();
533
  }
534
 
535
  // Hide suggestion buttons when chat starts
536
- const suggestionContainer = document.querySelector('.max-w-screen-md.mx-auto.px-4.md\\:px-0.mb-6');
537
- if (suggestionContainer && !suggestionContainer.classList.contains('hidden')) {
538
- suggestionContainer.classList.add('hidden');
 
 
 
 
 
539
  }
540
 
541
- const messageDiv = document.createElement('div');
542
- messageDiv.className = 'max-w-screen-md mx-auto mb-6';
543
 
544
  const formattedContent = isUser ? content : formatMessage(content);
545
 
546
  const html = `
547
- <div class="flex items-start ${isUser ? 'justify-end' : ''}">
548
- ${!isUser ? `
549
- <div class="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center mr-2">
550
- <span class="font-semibold text-sm">AR</span>
551
- </div>
552
- ` : ''}
553
- <div class="${isUser ? 'bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-100' : 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200'} p-3 rounded-lg max-w-[80%] message-content">
554
- ${formattedContent}
555
- </div>
556
- ${isUser ? `
557
- <div class="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center text-blue-800 dark:text-blue-200 ml-2">
558
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/></svg>
559
- </div>
560
- ` : ''}
561
- </div>
562
- `;
 
 
 
 
 
 
 
 
 
563
 
564
  messageDiv.innerHTML = html;
565
  chatContainer.appendChild(messageDiv);
@@ -570,17 +656,17 @@
570
 
571
  // Function to show loading indicator
572
  function showLoading() {
573
- loadingIndicator.classList.remove('hidden');
574
  chatContainer.scrollTop = chatContainer.scrollHeight;
575
  }
576
 
577
  // Function to hide loading indicator
578
  function hideLoading() {
579
- loadingIndicator.classList.add('hidden');
580
  }
581
 
582
  // Handle form submission
583
- chatForm.addEventListener('submit', async (e) => {
584
  e.preventDefault();
585
 
586
  const message = userInput.value.trim();
@@ -590,34 +676,34 @@
590
  addMessage(message, true);
591
 
592
  // Clear input
593
- userInput.value = '';
594
 
595
  // Show loading indicator
596
  showLoading();
597
 
598
  try {
599
  // Send message to API
600
- const verifiedId = sessionStorage.getItem('verifiedId');
601
- const response = await fetch('/chat', {
602
- method: 'POST',
603
  headers: {
604
- 'Content-Type': 'application/json',
605
- 'Authorization': `Bearer ${verifiedId}`
606
  },
607
  body: JSON.stringify({
608
  message: message,
609
- model: selectedModel
610
  }),
611
  });
612
 
613
  if (!response.ok) {
614
  // If unauthorized, redirect to login
615
  if (response.status === 401) {
616
- sessionStorage.removeItem('verifiedId');
617
- window.location.href = '/login';
618
  return;
619
  }
620
- throw new Error('API request failed');
621
  }
622
 
623
  const data = await response.json();
@@ -627,30 +713,33 @@
627
 
628
  // Add AI response to chat
629
  addMessage(data.response);
630
-
631
  } catch (error) {
632
- console.error('Error:', error);
633
 
634
  // Hide loading indicator
635
  hideLoading();
636
 
637
  // Add error message
638
- addMessage('Sorry, there was an error processing your request. Please try again.');
 
 
639
  }
640
  });
641
 
642
  // Handle suggestion button clicks
643
- suggestionButtons.forEach(button => {
644
- button.addEventListener('click', () => {
645
- const mainText = button.querySelector('.font-medium').textContent;
646
- const subText = button.querySelector('.text-gray-500, .text-gray-400').textContent;
 
 
647
  const fullText = `${mainText} ${subText}`;
648
 
649
  // Set the input value
650
  userInput.value = fullText;
651
 
652
  // Submit the form
653
- chatForm.dispatchEvent(new Event('submit'));
654
  });
655
  });
656
 
 
2
  <html lang="en" class="light">
3
 
4
  <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="arcana-project-verification" content="bac75f10-2276-42b3-a839-16ae98baa57c" />
8
  <title>AI Chat Interface</title>
9
  <script src="https://cdn.tailwindcss.com"></script>
10
  <script>
11
  tailwind.config = {
12
+ darkMode: "class",
13
  theme: {
14
  extend: {
15
  colors: {
16
  primary: {
17
+ DEFAULT: "#3b82f6",
18
+ foreground: "#ffffff",
19
  },
20
  secondary: {
21
+ DEFAULT: "#f3f4f6",
22
+ foreground: "#1f2937",
23
  },
24
+ },
25
+ },
26
+ },
27
+ };
28
  </script>
29
+ <script data-arcana-publisher-key="arc-pub-f8066482a332b510a0dd998dc1c614d419582f2e7326f69236602562c9d419cf"
30
+ src="https://js.arcana.ad/arcana.js"></script>
31
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
32
+ rel="stylesheet" />
33
  </head>
34
 
35
  <body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-['Inter'] transition-colors duration-200">
 
38
  <header class="border-b border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800">
39
  <div class="max-w-screen-xl mx-auto flex items-center justify-between">
40
  <div class="flex items-center">
41
+ <h1 class="text-xl font-medium">
42
+ OpenGPT with
43
+ <svg style="
44
+ display: inline-block;
45
+ vertical-align: -16.75%;
46
+ margin-right: -1.5%;
47
+ " stroke_linecap="round" stroke_linejoin="round" width="32" height="32" viewBox="0 0 24 24"
48
+ xmlns="http://www.w3.org/2000/svg">
49
  <defs>
50
+ <linearGradient id="logo-grad-arm" x1="0%" y1="0%" x2="100%" y2="0%">
51
+ <stop offset="0%" style="stop-color: #f7cd1b; stop-opacity: 1" />
52
+ <stop offset="100%" style="stop-color: #f9800c; stop-opacity: 1" />
53
  </linearGradient>
54
  </defs>
55
+ <g class="tooltip" style="display: inline">
56
+ <path fill="#067ff3"
57
+ d="M.227 20.705a5.444 5.444 0 0 0 4.745-2.858l4.48-8.13L7.67 6.613.03 20.368a.227.227 0 0 0 .198.337z" />
58
+ <path fill="#07b682"
59
+ d="M16.003 13.074l-2.747 1.361 1.944 3.39a5.697 5.682-.012 0 0 4.935 2.869.19.19 0 0 0 .165-.286z" />
60
+ <path fill="url(#logo-grad-arm)"
61
+ d="M7.99 14.555L6.2 17.872a.03.03 0 0 0 .04.042l17.744-8.798a.03.03 0 0 0-.022-.055l-11.67 3.765-3.851 1.344a.819.819 0 0 0-.451.385z" />
62
+ <path class="logo-arm"
63
+ d="M10.011 3.3a.683.681-.012 0 0-.733.339L8.19 5.603l4.137 7.212 2.964-.956-4.825-8.234a.683.681-.012 0 0-.455-.324z" />
64
  </g>
65
  <style>
66
  @media (prefers-color-scheme: light) {
67
  .logo-arm {
68
+ fill: black;
 
69
  }
70
  }
71
 
72
  @media (prefers-color-scheme: dark) {
73
  .logo-arm {
74
+ fill: white;
75
  }
76
  }
77
  </style>
78
+ </svg><span class="font-semibold" style="color: #067ff3">rc</span><span class="font-semibold"
79
+ style="color: #f9800c">a</span><span class="font-semibold" style="color: #07b682">na</span>
80
+ </h1>
81
  </div>
82
  <!-- -->
83
 
 
91
  <a href="https://arcana.ad/"
92
  class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white font-medium">Home</a>
93
  <button id="logout-button"
94
+ class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white font-medium">
95
+ Logout
96
+ </button>
97
  </nav>
98
  </div>
99
  </header>
 
116
  <ul class="py-1">
117
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
118
  data-value="gemma2">
119
+ Gemma2
120
+ </li>
121
  </ul>
122
  <ul class="py-1">
123
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
124
  data-value="llama3_1">
125
+ Llama3.1
126
+ </li>
127
  </ul>
128
  <ul class="py-1">
129
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
130
  data-value="llama3_3">
131
+ Llama3.3
132
+ </li>
133
  </ul>
134
  <ul class="py-1">
135
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
136
  data-value="qwen2_5">
137
+ Qwen2.5
138
+ </li>
139
  </ul>
140
  <ul class="py-1">
141
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
142
  data-value="deepseek_r1">
143
+ Deepseek_R1
144
+ </li>
145
  </ul>
146
  <ul class="py-1">
147
  <li class="model-option px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-800 dark:text-gray-200"
148
  data-value="gemma3">
149
+ Gemma3
150
+ </li>
151
  </ul>
152
  </div>
153
  </div>
 
195
  <button
196
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
197
  <div class="font-medium">Create a short 3 scene video script</div>
198
+ <div class="text-gray-500 text-sm">
199
+ set in a cyberpunk world run by AI
200
+ </div>
201
  </button>
202
  <button
203
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
204
  <div class="font-medium">Write a python code</div>
205
+ <div class="text-gray-500 text-sm">
206
+ for a simple, functional web app
207
+ </div>
208
  </button>
209
  <button
210
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
211
  <div class="font-medium">Help me study</div>
212
+ <div class="text-gray-500 text-sm">
213
+ vocabulary for a college entrance exam
214
+ </div>
215
  </button>
216
  <button
217
  class="text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-4 rounded-xl transition-colors">
218
  <div class="font-medium">Give me ideas</div>
219
+ <div class="text-gray-500 text-sm">
220
+ for a LinkedIn post for my recent promotion
221
+ </div>
222
  </button>
223
  </div>
224
 
225
  <!-- Input area -->
226
+ <div class="border-t border-gray-200 dark:border-gray-700 px-4 pt-4 bg-white dark:bg-gray-800">
227
  <div class="max-w-screen-md mx-auto">
228
  <form id="chat-form" class="relative">
229
  <div
 
239
  </button>
240
  <input type="text" id="user-input"
241
  class="flex-1 py-3 px-2 bg-transparent focus:outline-none text-gray-800 dark:text-gray-200"
242
+ placeholder="Send a Message" autocomplete="off" />
243
  <button type="button"
244
  class="p-3 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300">
245
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
 
261
  </button>
262
  </div>
263
  </form>
 
 
 
 
 
264
  </div>
265
  </div>
266
 
267
  <footer class="text-center p-4 text-gray-500 dark:text-gray-400 dark:bg-gray-800 text-sm">
268
+ <p class="text-gray-400 dark:text-gray-500 p-1">
269
+ LLMs can make mistakes. Verify important information.
270
+ </p>
271
+ <p class="p-1">© 2025 Arcana Inc. All rights reserved.</p>
272
  </footer>
273
  </main>
274
 
 
329
 
330
  .message-content em {
331
  font-style: normal;
332
+ color: #4b5563;
333
  }
334
 
335
  .dark .message-content em {
336
+ color: #9ca3af;
337
  }
338
 
339
  .message-content code {
340
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
341
  background-color: #f1f5f9;
342
  padding: 0.1em 0.3em;
343
  border-radius: 0.25rem;
 
364
  border-color: #334155;
365
  }
366
 
 
367
  .message-content pre code {
368
  background-color: transparent;
369
  padding: 0;
 
398
  </style>
399
 
400
  <script>
401
+ document.addEventListener("DOMContentLoaded", () => {
402
  const html = document.documentElement;
403
 
404
  // Function to set theme based on system preference
405
  function setThemeBasedOnSystemPreference() {
406
+ const prefersDark = window.matchMedia(
407
+ "(prefers-color-scheme: dark)"
408
+ ).matches;
409
  if (prefersDark) {
410
+ html.classList.add("dark");
411
  } else {
412
+ html.classList.remove("dark");
413
  }
414
  }
415
 
 
417
  setThemeBasedOnSystemPreference();
418
 
419
  // Listen for system preference changes
420
+ window
421
+ .matchMedia("(prefers-color-scheme: dark)")
422
+ .addEventListener("change", () => {
423
+ setThemeBasedOnSystemPreference();
424
+ });
425
 
426
  // Check if user is logged in
427
+ const verifiedId = sessionStorage.getItem("verifiedId");
428
  if (!verifiedId) {
429
+ window.location.href = "/login";
430
  return;
431
  }
432
 
433
  // Logout functionality
434
+ const logoutButton = document.getElementById("logout-button");
435
+ logoutButton.addEventListener("click", () => {
436
+ sessionStorage.removeItem("verifiedId");
437
+ window.location.href = "/login";
438
  });
439
 
440
+ const chatForm = document.getElementById("chat-form");
441
+ const userInput = document.getElementById("user-input");
442
+ const chatContainer = document.getElementById("chat-container");
443
+ const loadingIndicator = document.getElementById("loading");
444
+ const suggestionButtons = document.querySelectorAll(
445
+ ".max-w-screen-md.mx-auto.px-4.md\\:px-0.mb-6 button"
446
+ );
447
 
448
  // Model selector elements
449
+ const modelDropdownButton = document.getElementById(
450
+ "model-dropdown-button"
451
+ );
452
+ const modelDropdown = document.getElementById("model-dropdown");
453
+ const modelOptions = document.querySelectorAll(".model-option");
454
+ const selectedModelText = document.getElementById(
455
+ "selected-model-text"
456
+ );
457
 
458
  // Default model
459
+ let selectedModel = "gemma2";
460
 
461
  // Toggle dropdown
462
+ modelDropdownButton.addEventListener("click", () => {
463
+ modelDropdown.classList.toggle("hidden");
464
  });
465
 
466
  // Close dropdown when clicking outside
467
+ document.addEventListener("click", (e) => {
468
+ if (
469
+ !modelDropdownButton.contains(e.target) &&
470
+ !modelDropdown.contains(e.target)
471
+ ) {
472
+ modelDropdown.classList.add("hidden");
473
  }
474
  });
475
 
476
  // Handle model selection
477
+ modelOptions.forEach((option) => {
478
+ option.addEventListener("click", () => {
479
+ selectedModel = option.getAttribute("data-value");
480
  selectedModelText.textContent = option.textContent;
481
+ modelDropdown.classList.add("hidden");
482
 
483
  // Add visual indication of selected model
484
+ modelOptions.forEach((opt) =>
485
+ opt.classList.remove(
486
+ "bg-blue-50",
487
+ "text-blue-700",
488
+ "dark:bg-blue-900/50",
489
+ "dark:text-blue-300"
490
+ )
491
+ );
492
+ option.classList.add(
493
+ "bg-blue-50",
494
+ "text-blue-700",
495
+ "dark:bg-blue-900/50",
496
+ "dark:text-blue-300"
497
+ );
498
  });
499
  });
500
 
 
503
 
504
  // Function to add a message to the chat
505
  function formatMessage(text) {
506
+ console.log(text)
507
  // First, preserve code blocks by replacing them with placeholders
508
  const codeBlocks = [];
509
+ text = text.replace(
510
+ /^```(?:([\w-]+)[ \t]*)?\r?\n([\s\S]*?)\r?\n```/gm,
511
+ function (match, lang, code) {
512
+ const id = codeBlocks.length;
513
+ codeBlocks.push({ lang: lang || null, code: code.trim() });
514
+ return `__CODE_BLOCK_${id}__`;
515
+ }
516
+ );
517
 
518
  // Handle inline code
519
  const inlineCodes = [];
520
+ text = text.replace(/`([^`]+)`/g, function (match, code) {
521
  const id = inlineCodes.length;
522
  inlineCodes.push(code);
523
  return `__INLINE_CODE_${id}__`;
524
  });
525
 
526
  // Replace ** with proper styling
527
+ text = text.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
528
 
529
  // Handle numbered lists
530
+ text = text.replace(
531
+ /(\d+\.\s[^*]*?)(?=\d+\.|$)/g,
532
+ '<div class="mb-3">$1</div>'
533
+ );
534
 
535
  // Handle sections with asterisks
536
+ text = text.replace(/\*(.*?)\*/g, "<em>$1</em>");
537
 
538
  // Handle unordered lists
539
+ text = text.replace(/^- (.*?)$/gm, "<li>$1</li>");
540
+ text = text.replace(
541
+ /<li>.*?<\/li>(?:\s*<li>.*?<\/li>)+/gs,
542
+ function (match) {
543
+ return "<ul>" + match + "</ul>";
544
+ }
545
+ );
546
 
547
  // Handle ordered lists
548
+ text = text.replace(/^(\d+)\. (.*?)$/gm, "<li>$2</li>");
549
+ text = text.replace(
550
+ /<li>.*?<\/li>(?:\s*<li>.*?<\/li>)+/gs,
551
+ function (match) {
552
+ return "<ol>" + match + "</ol>";
553
+ }
554
+ );
555
 
556
  // Handle blockquotes
557
+ text = text.replace(/^> (.*?)$/gm, "<blockquote>$1</blockquote>");
558
 
559
  // Add proper spacing between sections
560
+ text = text
561
+ .split("\n")
562
+ .map((line) => line.trim())
563
+ .filter((line) => line)
564
+ .join("<p></p>");
565
 
566
  // Restore code blocks
567
+ codeBlocks.forEach((codeBlock, i) => {
568
+ text = text.replace(
569
+ `__CODE_BLOCK_${i}__`,
570
+ `<pre><code class="${escapeHtml(codeBlock.lang) ?? "plaintext"
571
+ }">${escapeHtml(codeBlock.code)}</code></pre>`
572
+ );
573
  });
574
 
575
  // Restore inline code
576
  inlineCodes.forEach((code, i) => {
577
+ text = text.replace(
578
+ `__INLINE_CODE_${i}__`,
579
+ `<code>${escapeHtml(code)}</code>`
580
+ );
581
  });
582
 
583
  return `<p>${text}</p>`;
 
585
 
586
  // Helper function to escape HTML in code blocks
587
  function escapeHtml(text) {
588
+ if (!text) return "";
589
  return text
590
  .replace(/&/g, "&amp;")
591
  .replace(/</g, "&lt;")
 
597
  // Function to add a message to the chat
598
  function addMessage(content, isUser = false) {
599
  // Hide the welcome message when chat starts
600
+ const welcomeMessage = chatContainer.querySelector(
601
+ ".flex.flex-col.items-center.justify-center"
602
+ );
603
  if (welcomeMessage) {
604
  welcomeMessage.remove();
605
  }
606
 
607
  // Hide suggestion buttons when chat starts
608
+ const suggestionContainer = document.querySelector(
609
+ ".max-w-screen-md.mx-auto.px-4.md\\:px-0.mb-6"
610
+ );
611
+ if (
612
+ suggestionContainer &&
613
+ !suggestionContainer.classList.contains("hidden")
614
+ ) {
615
+ suggestionContainer.classList.add("hidden");
616
  }
617
 
618
+ const messageDiv = document.createElement("div");
619
+ messageDiv.className = "max-w-screen-md mx-auto mb-6";
620
 
621
  const formattedContent = isUser ? content : formatMessage(content);
622
 
623
  const html = `
624
+ <div class="flex items-start ${isUser ? "justify-end" : ""}">
625
+ ${!isUser
626
+ ? `
627
+ <div class="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center mr-2">
628
+ <span class="font-semibold text-sm">AR</span>
629
+ </div>
630
+ `
631
+ : ""
632
+ }
633
+ <div class="${isUser
634
+ ? "bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-100"
635
+ : "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200"
636
+ } p-3 rounded-lg max-w-[80%] message-content">
637
+ ${formattedContent}
638
+ </div>
639
+ ${isUser
640
+ ? `
641
+ <div class="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center text-blue-800 dark:text-blue-200 ml-2">
642
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/></svg>
643
+ </div>
644
+ `
645
+ : ""
646
+ }
647
+ </div>
648
+ `;
649
 
650
  messageDiv.innerHTML = html;
651
  chatContainer.appendChild(messageDiv);
 
656
 
657
  // Function to show loading indicator
658
  function showLoading() {
659
+ loadingIndicator.classList.remove("hidden");
660
  chatContainer.scrollTop = chatContainer.scrollHeight;
661
  }
662
 
663
  // Function to hide loading indicator
664
  function hideLoading() {
665
+ loadingIndicator.classList.add("hidden");
666
  }
667
 
668
  // Handle form submission
669
+ chatForm.addEventListener("submit", async (e) => {
670
  e.preventDefault();
671
 
672
  const message = userInput.value.trim();
 
676
  addMessage(message, true);
677
 
678
  // Clear input
679
+ userInput.value = "";
680
 
681
  // Show loading indicator
682
  showLoading();
683
 
684
  try {
685
  // Send message to API
686
+ const verifiedId = sessionStorage.getItem("verifiedId");
687
+ const response = await fetch("/chat", {
688
+ method: "POST",
689
  headers: {
690
+ "Content-Type": "application/json",
691
+ Authorization: `Bearer ${verifiedId}`,
692
  },
693
  body: JSON.stringify({
694
  message: message,
695
+ model: selectedModel,
696
  }),
697
  });
698
 
699
  if (!response.ok) {
700
  // If unauthorized, redirect to login
701
  if (response.status === 401) {
702
+ sessionStorage.removeItem("verifiedId");
703
+ window.location.href = "/login";
704
  return;
705
  }
706
+ throw new Error("API request failed");
707
  }
708
 
709
  const data = await response.json();
 
713
 
714
  // Add AI response to chat
715
  addMessage(data.response);
 
716
  } catch (error) {
717
+ console.error("Error:", error);
718
 
719
  // Hide loading indicator
720
  hideLoading();
721
 
722
  // Add error message
723
+ addMessage(
724
+ "Sorry, there was an error processing your request. Please try again."
725
+ );
726
  }
727
  });
728
 
729
  // Handle suggestion button clicks
730
+ suggestionButtons.forEach((button) => {
731
+ button.addEventListener("click", () => {
732
+ const mainText = button.querySelector(".font-medium").textContent;
733
+ const subText = button.querySelector(
734
+ ".text-gray-500, .text-gray-400"
735
+ ).textContent;
736
  const fullText = `${mainText} ${subText}`;
737
 
738
  // Set the input value
739
  userInput.value = fullText;
740
 
741
  // Submit the form
742
+ chatForm.dispatchEvent(new Event("submit"));
743
  });
744
  });
745
 
app/static/login.html CHANGED
@@ -2,30 +2,34 @@
2
  <html lang="en" class="light">
3
 
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
7
  <title>Login - AI Chat Assistant</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script>
10
  tailwind.config = {
11
- darkMode: 'class',
12
  theme: {
13
  extend: {
14
  colors: {
15
  primary: {
16
- DEFAULT: '#3b82f6',
17
- foreground: '#ffffff',
18
  },
19
  secondary: {
20
- DEFAULT: '#f3f4f6',
21
- foreground: '#1f2937',
22
  },
23
- }
24
- }
25
- }
26
- }
27
  </script>
28
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
 
 
 
29
  </head>
30
 
31
  <body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-['Inter'] transition-colors duration-200">
@@ -33,42 +37,45 @@
33
  <header class="border-b border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800">
34
  <div class="max-w-screen-xl mx-auto flex items-center justify-between">
35
  <div class="flex items-center">
36
- <h1 class="text-xl font-medium">OpenGPT with <svg
37
- style='display: inline-block; vertical-align: -16.75%; margin-right: -1.5%;'
38
- stroke_linecap='round' stroke_linejoin='round' width='32' height='32' viewBox='0 0 24 24'
39
- xmlns='http://www.w3.org/2000/svg'>
 
 
 
 
40
  <defs>
41
- <linearGradient id='logo-grad-arm' x1='0%' y1='0%' x2='100%' y2='0%'>
42
- <stop offset='0%' style='stop-color:#f7cd1b; stop-opacity:1' />
43
- <stop offset='100%' style='stop-color:#f9800c; stop-opacity:1' />
44
  </linearGradient>
45
  </defs>
46
  <g>
47
- <path fill='#067ff3'
48
- d='M.227 20.705a5.444 5.444 0 0 0 4.745-2.858l4.48-8.13L7.67 6.613.03 20.368a.227.227 0 0 0 .198.337z' />
49
- <path fill='#07b682'
50
- d='M16.003 13.074l-2.747 1.361 1.944 3.39a5.697 5.682-.012 0 0 4.935 2.869.19.19 0 0 0 .165-.286z' />
51
- <path fill='url(#logo-grad-arm)'
52
- d='M7.99 14.555L6.2 17.872a.03.03 0 0 0 .04.042l17.744-8.798a.03.03 0 0 0-.022-.055l-11.67 3.765-3.851 1.344a.819.819 0 0 0-.451.385z' />
53
- <path class='logo-arm'
54
- d='M10.011 3.3a.683.681-.012 0 0-.733.339L8.19 5.603l4.137 7.212 2.964-.956-4.825-8.234a.683.681-.012 0 0-.455-.324z' />
55
  </g>
56
  <style>
57
  @media (prefers-color-scheme: light) {
58
  .logo-arm {
59
-
60
- fill: black
61
  }
62
  }
63
 
64
  @media (prefers-color-scheme: dark) {
65
  .logo-arm {
66
- fill: white
67
  }
68
  }
69
  </style>
70
- </svg><span class="font-semibold" style="color: #067ff3;">rc</span><span class="font-semibold"
71
- style="color: #f9800c;">a</span><span class="font-semibold" style="color: #07b682;">na</span>
72
  </h1>
73
  </div>
74
 
@@ -84,8 +91,8 @@
84
  </div>
85
  </header>
86
 
87
- <main class="flex-1 flex items-center justify-center p-4">
88
- <div class="w-full max-w-md">
89
  <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md dark:shadow-gray-700/30 p-5 sm:p-8">
90
  <div class="text-center mb-6">
91
  <div
@@ -93,11 +100,12 @@
93
  AR
94
  </div>
95
  <h2 class="text-xl sm:text-2xl font-semibold">Welcome</h2>
96
- <p class="text-gray-500 dark:text-gray-400 text-sm sm:text-base">Sign in to access the <span
97
- class="font-medium">OpenGPT with </span><span class="font-semibold"
98
- style="color: #067ff3;">Ar</span><span class="font-semibold"
99
- style="color: #f7cd1b;">ca</span><span class="font-semibold"
100
- style="color: #07b682;">na</span>
 
101
  </p>
102
  </div>
103
 
@@ -111,7 +119,7 @@
111
  ID</label>
112
  <input type="email" id="email" name="email_id"
113
  class="w-full px-3 py-2 sm:px-4 sm:py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-base bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
114
- placeholder="Enter your email" required>
115
  </div>
116
 
117
  <div>
@@ -119,7 +127,7 @@
119
  class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Access Key</label>
120
  <input type="password" id="access-key" name="access_key"
121
  class="w-full px-3 py-2 sm:px-4 sm:py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-base bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
122
- placeholder="Enter your access key" required>
123
  </div>
124
 
125
  <button type="submit" id="login-button"
@@ -129,8 +137,9 @@
129
  </form>
130
 
131
  <div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
132
- <p class="text-gray-600 dark:text-gray-400 mb-3 text-center text-sm sm:text-base">Don't have an
133
- access key?</p>
 
134
  <a href="https://forms.gle/Xzv6Qsh2GUbd9EZc6" id="request-access-button"
135
  class="flex items-center justify-center w-full bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-indigo-900/30 dark:to-blue-900/30 text-blue-600 dark:text-blue-400 py-2.5 px-4 rounded-md hover:from-indigo-100 hover:to-blue-100 dark:hover:from-indigo-900/50 dark:hover:to-blue-900/50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition-all duration-200 border border-blue-200 dark:border-blue-800 shadow-sm text-sm sm:text-base font-medium"
136
  target="_blank">
@@ -147,23 +156,28 @@
147
  </div>
148
  </div>
149
  </div>
150
- </main>
151
 
152
- <footer class="text-center p-4 text-gray-500 dark:text-gray-400 dark:bg-gray-800 text-sm">
153
- <p 2025 Arcana Inc. All rights reserved.</p>
154
- </footer>
 
 
 
 
155
 
156
  <script>
157
- document.addEventListener('DOMContentLoaded', () => {
158
  const html = document.documentElement;
159
 
160
  // Function to set theme based on system preference
161
  function setThemeBasedOnSystemPreference() {
162
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
 
 
163
  if (prefersDark) {
164
- html.classList.add('dark');
165
  } else {
166
- html.classList.remove('dark');
167
  }
168
  }
169
 
@@ -171,76 +185,79 @@
171
  setThemeBasedOnSystemPreference();
172
 
173
  // Listen for system preference changes
174
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
175
- setThemeBasedOnSystemPreference();
176
- });
 
 
177
 
178
  // Check if user is already logged in
179
- const verifiedId = sessionStorage.getItem('verifiedId');
180
  if (verifiedId) {
181
- window.location.href = '/';
182
  return;
183
  }
184
 
185
- const loginForm = document.getElementById('login-form');
186
- const loginButton = document.getElementById('login-button');
187
- const errorMessage = document.getElementById('error-message');
188
 
189
- loginForm.addEventListener('submit', async (e) => {
190
  e.preventDefault();
191
 
192
  // Get form data
193
- const email = document.getElementById('email').value.trim();
194
- const accessKey = document.getElementById('access-key').value.trim();
195
 
196
  if (!email || !accessKey) {
197
- showError('Please enter both email and access key');
198
  return;
199
  }
200
 
201
  // Disable button and show loading state
202
  loginButton.disabled = true;
203
- loginButton.innerHTML = 'Signing in...';
204
- errorMessage.classList.add('hidden');
205
 
206
  try {
207
  // Send login request
208
- const response = await fetch('/login', {
209
- method: 'POST',
210
  headers: {
211
- 'Content-Type': 'application/json',
212
  },
213
  body: JSON.stringify({
214
  email_id: email,
215
- access_key: accessKey
216
  }),
217
  });
218
 
219
  const data = await response.json();
220
 
221
  if (!response.ok) {
222
- throw new Error(data.message || 'Login failed. Please check your credentials.');
 
 
223
  }
224
 
225
  // Store auth token or user info
226
- sessionStorage.setItem('verifiedId', data.verified_id);
227
 
228
  // Redirect to chat page
229
- window.location.href = '/';
230
-
231
  } catch (error) {
232
- console.error('Login error:', error);
233
- showError(error.message || 'Login failed. Please try again.');
234
 
235
  // Reset button state
236
  loginButton.disabled = false;
237
- loginButton.innerHTML = 'Sign In';
238
  }
239
  });
240
 
241
  function showError(message) {
242
  errorMessage.textContent = message;
243
- errorMessage.classList.remove('hidden');
244
  }
245
  });
246
  </script>
 
2
  <html lang="en" class="light">
3
 
4
  <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="arcana-project-verification" content="43544d63-608c-4934-91e7-8543c6f5d1e6" />
8
  <title>Login - AI Chat Assistant</title>
9
  <script src="https://cdn.tailwindcss.com"></script>
10
  <script>
11
  tailwind.config = {
12
+ darkMode: "class",
13
  theme: {
14
  extend: {
15
  colors: {
16
  primary: {
17
+ DEFAULT: "#3b82f6",
18
+ foreground: "#ffffff",
19
  },
20
  secondary: {
21
+ DEFAULT: "#f3f4f6",
22
+ foreground: "#1f2937",
23
  },
24
+ },
25
+ },
26
+ },
27
+ };
28
  </script>
29
+ <script data-arcana-publisher-key="arc-pub-f8066482a332b510a0dd998dc1c614d419582f2e7326f69236602562c9d419cf"
30
+ src="https://js.arcana.ad/arcana.js"></script>
31
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
32
+ rel="stylesheet" />
33
  </head>
34
 
35
  <body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 font-['Inter'] transition-colors duration-200">
 
37
  <header class="border-b border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800">
38
  <div class="max-w-screen-xl mx-auto flex items-center justify-between">
39
  <div class="flex items-center">
40
+ <h1 class="text-xl font-medium">
41
+ OpenGPT with
42
+ <svg style="
43
+ display: inline-block;
44
+ vertical-align: -16.75%;
45
+ margin-right: -1.5%;
46
+ " stroke_linecap="round" stroke_linejoin="round" width="32" height="32" viewBox="0 0 24 24"
47
+ xmlns="http://www.w3.org/2000/svg">
48
  <defs>
49
+ <linearGradient id="logo-grad-arm" x1="0%" y1="0%" x2="100%" y2="0%">
50
+ <stop offset="0%" style="stop-color: #f7cd1b; stop-opacity: 1" />
51
+ <stop offset="100%" style="stop-color: #f9800c; stop-opacity: 1" />
52
  </linearGradient>
53
  </defs>
54
  <g>
55
+ <path fill="#067ff3"
56
+ d="M.227 20.705a5.444 5.444 0 0 0 4.745-2.858l4.48-8.13L7.67 6.613.03 20.368a.227.227 0 0 0 .198.337z" />
57
+ <path fill="#07b682"
58
+ d="M16.003 13.074l-2.747 1.361 1.944 3.39a5.697 5.682-.012 0 0 4.935 2.869.19.19 0 0 0 .165-.286z" />
59
+ <path fill="url(#logo-grad-arm)"
60
+ d="M7.99 14.555L6.2 17.872a.03.03 0 0 0 .04.042l17.744-8.798a.03.03 0 0 0-.022-.055l-11.67 3.765-3.851 1.344a.819.819 0 0 0-.451.385z" />
61
+ <path class="logo-arm"
62
+ d="M10.011 3.3a.683.681-.012 0 0-.733.339L8.19 5.603l4.137 7.212 2.964-.956-4.825-8.234a.683.681-.012 0 0-.455-.324z" />
63
  </g>
64
  <style>
65
  @media (prefers-color-scheme: light) {
66
  .logo-arm {
67
+ fill: black;
 
68
  }
69
  }
70
 
71
  @media (prefers-color-scheme: dark) {
72
  .logo-arm {
73
+ fill: white;
74
  }
75
  }
76
  </style>
77
+ </svg><span class="font-semibold" style="color: #067ff3">rc</span><span class="font-semibold"
78
+ style="color: #f9800c">a</span><span class="font-semibold" style="color: #07b682">na</span>
79
  </h1>
80
  </div>
81
 
 
91
  </div>
92
  </header>
93
 
94
+ <main class="flex flex-col items-center justify-center h-screen">
95
+ <div class="w-full max-w-md p-4">
96
  <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md dark:shadow-gray-700/30 p-5 sm:p-8">
97
  <div class="text-center mb-6">
98
  <div
 
100
  AR
101
  </div>
102
  <h2 class="text-xl sm:text-2xl font-semibold">Welcome</h2>
103
+ <p class="text-gray-500 dark:text-gray-400 text-sm sm:text-base">
104
+ Sign in to access the
105
+ <span class="font-semibold" style="color: #067ff3">Ar</span><span class="font-semibold"
106
+ style="color: #f9800c">ca</span><span class="font-semibold" style="color: #07b682">na</span>
107
+ in
108
+ Action
109
  </p>
110
  </div>
111
 
 
119
  ID</label>
120
  <input type="email" id="email" name="email_id"
121
  class="w-full px-3 py-2 sm:px-4 sm:py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-base bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
122
+ placeholder="Enter your email" required />
123
  </div>
124
 
125
  <div>
 
127
  class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Access Key</label>
128
  <input type="password" id="access-key" name="access_key"
129
  class="w-full px-3 py-2 sm:px-4 sm:py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-base bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
130
+ placeholder="Enter your access key" required />
131
  </div>
132
 
133
  <button type="submit" id="login-button"
 
137
  </form>
138
 
139
  <div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
140
+ <p class="text-gray-600 dark:text-gray-400 mb-3 text-center text-sm sm:text-base">
141
+ Don't have an access key?
142
+ </p>
143
  <a href="https://forms.gle/Xzv6Qsh2GUbd9EZc6" id="request-access-button"
144
  class="flex items-center justify-center w-full bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-indigo-900/30 dark:to-blue-900/30 text-blue-600 dark:text-blue-400 py-2.5 px-4 rounded-md hover:from-indigo-100 hover:to-blue-100 dark:hover:from-indigo-900/50 dark:hover:to-blue-900/50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition-all duration-200 border border-blue-200 dark:border-blue-800 shadow-sm text-sm sm:text-base font-medium"
145
  target="_blank">
 
156
  </div>
157
  </div>
158
  </div>
 
159
 
160
+ <footer class="text-center p-4 text-gray-500 dark:text-gray-400 dark:bg-gray-800 text-sm">
161
+ <p class="text-gray-400 dark:text-gray-500 p-1">
162
+ LLMs can make mistakes. Verify important information.
163
+ </p>
164
+ <p class="p-1">© 2025 Arcana Inc. All rights reserved.</p>
165
+ </footer>
166
+ </main>
167
 
168
  <script>
169
+ document.addEventListener("DOMContentLoaded", () => {
170
  const html = document.documentElement;
171
 
172
  // Function to set theme based on system preference
173
  function setThemeBasedOnSystemPreference() {
174
+ const prefersDark = window.matchMedia(
175
+ "(prefers-color-scheme: dark)"
176
+ ).matches;
177
  if (prefersDark) {
178
+ html.classList.add("dark");
179
  } else {
180
+ html.classList.remove("dark");
181
  }
182
  }
183
 
 
185
  setThemeBasedOnSystemPreference();
186
 
187
  // Listen for system preference changes
188
+ window
189
+ .matchMedia("(prefers-color-scheme: dark)")
190
+ .addEventListener("change", () => {
191
+ setThemeBasedOnSystemPreference();
192
+ });
193
 
194
  // Check if user is already logged in
195
+ const verifiedId = sessionStorage.getItem("verifiedId");
196
  if (verifiedId) {
197
+ window.location.href = "/";
198
  return;
199
  }
200
 
201
+ const loginForm = document.getElementById("login-form");
202
+ const loginButton = document.getElementById("login-button");
203
+ const errorMessage = document.getElementById("error-message");
204
 
205
+ loginForm.addEventListener("submit", async (e) => {
206
  e.preventDefault();
207
 
208
  // Get form data
209
+ const email = document.getElementById("email").value.trim();
210
+ const accessKey = document.getElementById("access-key").value.trim();
211
 
212
  if (!email || !accessKey) {
213
+ showError("Please enter both email and access key");
214
  return;
215
  }
216
 
217
  // Disable button and show loading state
218
  loginButton.disabled = true;
219
+ loginButton.innerHTML = "Signing in...";
220
+ errorMessage.classList.add("hidden");
221
 
222
  try {
223
  // Send login request
224
+ const response = await fetch("/login", {
225
+ method: "POST",
226
  headers: {
227
+ "Content-Type": "application/json",
228
  },
229
  body: JSON.stringify({
230
  email_id: email,
231
+ access_key: accessKey,
232
  }),
233
  });
234
 
235
  const data = await response.json();
236
 
237
  if (!response.ok) {
238
+ throw new Error(
239
+ data.message || "Login failed. Please check your credentials."
240
+ );
241
  }
242
 
243
  // Store auth token or user info
244
+ sessionStorage.setItem("verifiedId", data.verified_id);
245
 
246
  // Redirect to chat page
247
+ window.location.href = "/";
 
248
  } catch (error) {
249
+ console.error("Login error:", error);
250
+ showError(error.message || "Login failed. Please try again.");
251
 
252
  // Reset button state
253
  loginButton.disabled = false;
254
+ loginButton.innerHTML = "Sign In";
255
  }
256
  });
257
 
258
  function showError(message) {
259
  errorMessage.textContent = message;
260
+ errorMessage.classList.remove("hidden");
261
  }
262
  });
263
  </script>
uv.lock CHANGED
@@ -71,7 +71,7 @@ requires-dist = [
71
  { name = "arcana-codex", specifier = ">=0.2" },
72
  { name = "email-validator", specifier = ">=2.2" },
73
  { name = "fastapi-slim", specifier = ">=0.115" },
74
- { name = "groq", specifier = ">=0.20.0" },
75
  { name = "huggingface-hub", specifier = ">=0.29" },
76
  { name = "llama-cpp-python", specifier = ">=0.3" },
77
  { name = "pillow", specifier = ">=11.1" },
 
71
  { name = "arcana-codex", specifier = ">=0.2" },
72
  { name = "email-validator", specifier = ">=2.2" },
73
  { name = "fastapi-slim", specifier = ">=0.115" },
74
+ { name = "groq", specifier = ">=0.20" },
75
  { name = "huggingface-hub", specifier = ">=0.29" },
76
  { name = "llama-cpp-python", specifier = ">=0.3" },
77
  { name = "pillow", specifier = ">=11.1" },