Spaces:
Running
Running
Commit
·
e5ab376
1
Parent(s):
8b0dded
Add support for analytics & do minor UI changes
Browse files- app/static/index.html +245 -156
- app/static/login.html +94 -77
- 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:
|
12 |
theme: {
|
13 |
extend: {
|
14 |
colors: {
|
15 |
primary: {
|
16 |
-
DEFAULT:
|
17 |
-
foreground:
|
18 |
},
|
19 |
secondary: {
|
20 |
-
DEFAULT:
|
21 |
-
foreground:
|
22 |
},
|
23 |
-
}
|
24 |
-
}
|
25 |
-
}
|
26 |
-
}
|
27 |
</script>
|
28 |
-
<
|
|
|
|
|
|
|
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">
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
41 |
<defs>
|
42 |
-
<linearGradient id=
|
43 |
-
<stop offset=
|
44 |
-
<stop offset=
|
45 |
</linearGradient>
|
46 |
</defs>
|
47 |
-
<g class=
|
48 |
-
<path fill=
|
49 |
-
d=
|
50 |
-
<path fill=
|
51 |
-
d=
|
52 |
-
<path fill=
|
53 |
-
d=
|
54 |
-
<path class=
|
55 |
-
d=
|
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
|
72 |
-
style="color: #f9800c
|
73 |
-
|
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">
|
|
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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">
|
|
|
|
|
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">
|
|
|
|
|
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">
|
|
|
|
|
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">
|
|
|
|
|
199 |
</button>
|
200 |
</div>
|
201 |
|
202 |
<!-- Input area -->
|
203 |
-
<div class="border-t border-gray-200 dark:border-gray-700
|
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
|
|
|
|
|
|
|
251 |
</footer>
|
252 |
</main>
|
253 |
|
@@ -308,15 +329,15 @@
|
|
308 |
|
309 |
.message-content em {
|
310 |
font-style: normal;
|
311 |
-
color: #
|
312 |
}
|
313 |
|
314 |
.dark .message-content em {
|
315 |
-
color: #
|
316 |
}
|
317 |
|
318 |
.message-content code {
|
319 |
-
font-family:
|
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(
|
382 |
const html = document.documentElement;
|
383 |
|
384 |
// Function to set theme based on system preference
|
385 |
function setThemeBasedOnSystemPreference() {
|
386 |
-
const prefersDark = window.matchMedia(
|
|
|
|
|
387 |
if (prefersDark) {
|
388 |
-
html.classList.add(
|
389 |
} else {
|
390 |
-
html.classList.remove(
|
391 |
}
|
392 |
}
|
393 |
|
@@ -395,61 +417,84 @@
|
|
395 |
setThemeBasedOnSystemPreference();
|
396 |
|
397 |
// Listen for system preference changes
|
398 |
-
window
|
399 |
-
|
400 |
-
|
|
|
|
|
401 |
|
402 |
// Check if user is logged in
|
403 |
-
const verifiedId = sessionStorage.getItem(
|
404 |
if (!verifiedId) {
|
405 |
-
window.location.href =
|
406 |
return;
|
407 |
}
|
408 |
|
409 |
// Logout functionality
|
410 |
-
const logoutButton = document.getElementById(
|
411 |
-
logoutButton.addEventListener(
|
412 |
-
sessionStorage.removeItem(
|
413 |
-
window.location.href =
|
414 |
});
|
415 |
|
416 |
-
const chatForm = document.getElementById(
|
417 |
-
const userInput = document.getElementById(
|
418 |
-
const chatContainer = document.getElementById(
|
419 |
-
const loadingIndicator = document.getElementById(
|
420 |
-
const suggestionButtons = document.querySelectorAll(
|
|
|
|
|
421 |
|
422 |
// Model selector elements
|
423 |
-
const modelDropdownButton = document.getElementById(
|
424 |
-
|
425 |
-
|
426 |
-
const
|
|
|
|
|
|
|
|
|
427 |
|
428 |
// Default model
|
429 |
-
let selectedModel =
|
430 |
|
431 |
// Toggle dropdown
|
432 |
-
modelDropdownButton.addEventListener(
|
433 |
-
modelDropdown.classList.toggle(
|
434 |
});
|
435 |
|
436 |
// Close dropdown when clicking outside
|
437 |
-
document.addEventListener(
|
438 |
-
if (
|
439 |
-
|
|
|
|
|
|
|
440 |
}
|
441 |
});
|
442 |
|
443 |
// Handle model selection
|
444 |
-
modelOptions.forEach(option => {
|
445 |
-
option.addEventListener(
|
446 |
-
selectedModel = option.getAttribute(
|
447 |
selectedModelText.textContent = option.textContent;
|
448 |
-
modelDropdown.classList.add(
|
449 |
|
450 |
// Add visual indication of selected model
|
451 |
-
modelOptions.forEach(opt =>
|
452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
|
|
|
|
|
|
468 |
|
469 |
// Handle inline code
|
470 |
const inlineCodes = [];
|
471 |
-
text = text
|
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,
|
479 |
|
480 |
// Handle numbered lists
|
481 |
-
text = text.replace(
|
|
|
|
|
|
|
482 |
|
483 |
// Handle sections with asterisks
|
484 |
-
text = text.replace(/\*(.*?)\*/g,
|
485 |
|
486 |
// Handle unordered lists
|
487 |
-
text = text.replace(/^- (.*?)$/gm,
|
488 |
-
text = text.replace(
|
489 |
-
|
490 |
-
|
|
|
|
|
|
|
491 |
|
492 |
// Handle ordered lists
|
493 |
-
text = text.replace(/^(\d+)\. (.*?)$/gm,
|
494 |
-
text = text.replace(
|
495 |
-
|
496 |
-
|
|
|
|
|
|
|
497 |
|
498 |
// Handle blockquotes
|
499 |
-
text = text.replace(/^> (.*?)$/gm,
|
500 |
|
501 |
// Add proper spacing between sections
|
502 |
-
text = text
|
|
|
|
|
|
|
|
|
503 |
|
504 |
// Restore code blocks
|
505 |
-
codeBlocks.forEach((
|
506 |
-
text = text.replace(
|
|
|
|
|
|
|
|
|
507 |
});
|
508 |
|
509 |
// Restore inline code
|
510 |
inlineCodes.forEach((code, i) => {
|
511 |
-
text = text.replace(
|
|
|
|
|
|
|
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, "&")
|
521 |
.replace(/</g, "<")
|
@@ -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(
|
|
|
|
|
531 |
if (welcomeMessage) {
|
532 |
welcomeMessage.remove();
|
533 |
}
|
534 |
|
535 |
// Hide suggestion buttons when chat starts
|
536 |
-
const suggestionContainer = document.querySelector(
|
537 |
-
|
538 |
-
|
|
|
|
|
|
|
|
|
|
|
539 |
}
|
540 |
|
541 |
-
const messageDiv = document.createElement(
|
542 |
-
messageDiv.className =
|
543 |
|
544 |
const formattedContent = isUser ? content : formatMessage(content);
|
545 |
|
546 |
const html = `
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
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(
|
574 |
chatContainer.scrollTop = chatContainer.scrollHeight;
|
575 |
}
|
576 |
|
577 |
// Function to hide loading indicator
|
578 |
function hideLoading() {
|
579 |
-
loadingIndicator.classList.add(
|
580 |
}
|
581 |
|
582 |
// Handle form submission
|
583 |
-
chatForm.addEventListener(
|
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(
|
601 |
-
const response = await fetch(
|
602 |
-
method:
|
603 |
headers: {
|
604 |
-
|
605 |
-
|
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(
|
617 |
-
window.location.href =
|
618 |
return;
|
619 |
}
|
620 |
-
throw new Error(
|
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(
|
633 |
|
634 |
// Hide loading indicator
|
635 |
hideLoading();
|
636 |
|
637 |
// Add error message
|
638 |
-
addMessage(
|
|
|
|
|
639 |
}
|
640 |
});
|
641 |
|
642 |
// Handle suggestion button clicks
|
643 |
-
suggestionButtons.forEach(button => {
|
644 |
-
button.addEventListener(
|
645 |
-
const mainText = button.querySelector(
|
646 |
-
const subText = button.querySelector(
|
|
|
|
|
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(
|
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, "&")
|
591 |
.replace(/</g, "<")
|
|
|
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:
|
12 |
theme: {
|
13 |
extend: {
|
14 |
colors: {
|
15 |
primary: {
|
16 |
-
DEFAULT:
|
17 |
-
foreground:
|
18 |
},
|
19 |
secondary: {
|
20 |
-
DEFAULT:
|
21 |
-
foreground:
|
22 |
},
|
23 |
-
}
|
24 |
-
}
|
25 |
-
}
|
26 |
-
}
|
27 |
</script>
|
28 |
-
<
|
|
|
|
|
|
|
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">
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
40 |
<defs>
|
41 |
-
<linearGradient id=
|
42 |
-
<stop offset=
|
43 |
-
<stop offset=
|
44 |
</linearGradient>
|
45 |
</defs>
|
46 |
<g>
|
47 |
-
<path fill=
|
48 |
-
d=
|
49 |
-
<path fill=
|
50 |
-
d=
|
51 |
-
<path fill=
|
52 |
-
d=
|
53 |
-
<path class=
|
54 |
-
d=
|
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
|
71 |
-
style="color: #f9800c
|
72 |
</h1>
|
73 |
</div>
|
74 |
|
@@ -84,8 +91,8 @@
|
|
84 |
</div>
|
85 |
</header>
|
86 |
|
87 |
-
<main class="flex
|
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">
|
97 |
-
|
98 |
-
|
99 |
-
style="color: #
|
100 |
-
|
|
|
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">
|
133 |
-
access key
|
|
|
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 |
-
|
153 |
-
|
154 |
-
|
|
|
|
|
|
|
|
|
155 |
|
156 |
<script>
|
157 |
-
document.addEventListener(
|
158 |
const html = document.documentElement;
|
159 |
|
160 |
// Function to set theme based on system preference
|
161 |
function setThemeBasedOnSystemPreference() {
|
162 |
-
const prefersDark = window.matchMedia(
|
|
|
|
|
163 |
if (prefersDark) {
|
164 |
-
html.classList.add(
|
165 |
} else {
|
166 |
-
html.classList.remove(
|
167 |
}
|
168 |
}
|
169 |
|
@@ -171,76 +185,79 @@
|
|
171 |
setThemeBasedOnSystemPreference();
|
172 |
|
173 |
// Listen for system preference changes
|
174 |
-
window
|
175 |
-
|
176 |
-
|
|
|
|
|
177 |
|
178 |
// Check if user is already logged in
|
179 |
-
const verifiedId = sessionStorage.getItem(
|
180 |
if (verifiedId) {
|
181 |
-
window.location.href =
|
182 |
return;
|
183 |
}
|
184 |
|
185 |
-
const loginForm = document.getElementById(
|
186 |
-
const loginButton = document.getElementById(
|
187 |
-
const errorMessage = document.getElementById(
|
188 |
|
189 |
-
loginForm.addEventListener(
|
190 |
e.preventDefault();
|
191 |
|
192 |
// Get form data
|
193 |
-
const email = document.getElementById(
|
194 |
-
const accessKey = document.getElementById(
|
195 |
|
196 |
if (!email || !accessKey) {
|
197 |
-
showError(
|
198 |
return;
|
199 |
}
|
200 |
|
201 |
// Disable button and show loading state
|
202 |
loginButton.disabled = true;
|
203 |
-
loginButton.innerHTML =
|
204 |
-
errorMessage.classList.add(
|
205 |
|
206 |
try {
|
207 |
// Send login request
|
208 |
-
const response = await fetch(
|
209 |
-
method:
|
210 |
headers: {
|
211 |
-
|
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(
|
|
|
|
|
223 |
}
|
224 |
|
225 |
// Store auth token or user info
|
226 |
-
sessionStorage.setItem(
|
227 |
|
228 |
// Redirect to chat page
|
229 |
-
window.location.href =
|
230 |
-
|
231 |
} catch (error) {
|
232 |
-
console.error(
|
233 |
-
showError(error.message ||
|
234 |
|
235 |
// Reset button state
|
236 |
loginButton.disabled = false;
|
237 |
-
loginButton.innerHTML =
|
238 |
}
|
239 |
});
|
240 |
|
241 |
function showError(message) {
|
242 |
errorMessage.textContent = message;
|
243 |
-
errorMessage.classList.remove(
|
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
|
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" },
|