CodeVed / index.html
Renderlib-dev's picture
Update index.html
ddd600e verified
Raw
History Blame Contribute Delete
129 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>CODE VED | Engineered by Divy Patel</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- Highlight.js CSS (डार्क थीम) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<!-- एक्सटर्नल लाइब्रेरीज़ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<!-- MathJax कॉन्फ़िगरेशन (गणित, भौतिकी, रसायन विज्ञान के लिए) -->
<script>
window.MathJax = {
tex: {
inlineMath: [
['$', '$'],
['\\(', '\\)']
],
displayMath: [
['$$', '$$'],
['\\[', '\\]']
],
processEscapes: true,
packages: {
'[+]': ['mhchem', 'physics', 'cancel', 'color', 'amsmath', 'amssymb', 'boldsymbol']
}
},
loader: {
load: ['[tex]/mhchem', '[tex]/physics', '[tex]/cancel', '[tex]/color', '[tex]/amsmath', '[tex]/amssymb', '[tex]/boldsymbol']
},
options: {
ignoreHtmlClass: 'tex2jax_ignore',
processHtmlClass: 'tex2jax_process'
},
startup: {
typeset: false
}
};
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<!-- सीएसएस स्टाइल्स -->
<style>
:root {
/* कलर्स और थीम्स */
--bg-main: #ffffff;
--bg-sidebar: rgba(240, 244, 249, 0.7);
--bg-user-msg: rgba(240, 244, 249, 0.8);
--text-primary: #1f1f1f;
--text-secondary: #444746;
--text-tertiary: #727775;
--border-light: rgba(116, 119, 117, 0.15);
--border-focus: #a8c7fa;
--brand-color: #0f172a;
--brand-accent: #0b57d0;
--brand-success: #146c2e;
--brand-danger: #b3261e;
--brand-warning: #e37400;
/* फोंट्स */
--font-ui: 'Inter', sans-serif;
--font-code: 'JetBrains Mono', monospace;
/* रेडियस और ट्रांज़िशन */
--radius-sm: 12px;
--radius-md: 18px;
--radius-lg: 32px;
--transition-default: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
/* ग्लासमोर्फिज़्म प्रभाव */
--glass-bg: rgba(255, 255, 255, 0.6);
--glass-border: rgba(255, 255, 255, 0.3);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
/* ग्लोबल रीसेट */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
*:focus {
outline: none !important;
box-shadow: none !important;
}
*:focus-visible {
outline: none !important;
box-shadow: none !important;
}
/* बॉडी और लेआउट */
html, body {
height: 100%;
height: 100dvh;
width: 100vw;
font-family: var(--font-ui);
color: var(--text-primary);
display: flex;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
body {
background: linear-gradient(180deg, #ffffff 0%, #ffffff 40%, #e3ecfa 100%);
position: relative;
}
/* बैकग्राउंड एनिमेशन */
body::before {
content: '';
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background:
radial-gradient(circle at 20% 30%, rgba(168, 199, 250, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(11, 87, 208, 0.1) 0%, transparent 50%),
radial-gradient(circle at 50% 50%, rgba(227, 236, 250, 0.2) 0%, transparent 70%);
animation: liquidMove 20s ease-in-out infinite;
pointer-events: none;
z-index: 0;
will-change: transform;
backface-visibility: hidden;
transform: translate3d(0, 0, 0);
}
@keyframes liquidMove {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
33% {
transform: translate(30px, -30px) rotate(120deg);
}
66% {
transform: translate(-20px, 20px) rotate(240deg);
}
}
button, a, [role="button"] {
-webkit-tap-highlight-color: transparent;
outline: none;
}
button {
background: none;
border: none;
cursor: pointer;
color: inherit;
font-family: inherit;
}
input, textarea {
font-family: inherit;
outline: none;
border: none;
background: transparent;
}
/* स्क्रॉलबार स्टाइलिंग */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(114, 119, 117, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(114, 119, 117, 0.6);
}
/* एनिमेशन कीफ्रेम्स */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulseOpacity {
0% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
@keyframes typingBounce {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.4;
}
30% {
transform: translateY(-4px);
opacity: 1;
}
}
@keyframes recordPulse {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(179, 38, 30, 0.4);
}
50% {
transform: scale(1.05);
box-shadow: 0 0 0 10px rgba(179, 38, 30, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(179, 38, 30, 0);
}
}
@keyframes dropUpFade {
from {
opacity: 0;
transform: translateY(10px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes blinkCursor {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.blinking-cursor {
display: inline-block;
width: 8px;
height: 20px;
background: var(--brand-accent);
margin-left: 2px;
vertical-align: text-bottom;
animation: blinkCursor 0.8s infinite;
border-radius: 2px;
}
/* मेन्यू बटन */
.btn-menu {
position: absolute;
top: 16px;
left: 16px;
z-index: 50;
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--glass-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border);
color: var(--text-secondary);
transition: var(--transition-default);
box-shadow: var(--glass-shadow);
}
.btn-menu:hover {
background: rgba(255, 255, 255, 0.8);
color: var(--text-primary);
}
/* साइडबार स्टाइल्स */
.sidebar {
width: 280px;
background: var(--bg-sidebar);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-right: 1px solid var(--glass-border);
display: flex;
flex-direction: column;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 100;
height: 100%;
flex-shrink: 0;
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.03);
}
.sidebar.collapsed {
transform: translateX(-100%);
position: absolute;
}
.sidebar-header {
padding: 20px 20px 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar-logo {
font-weight: 600;
font-size: 16px;
letter-spacing: -0.3px;
color: var(--text-primary);
}
.btn-new-chat {
margin: 10px 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 12px 14px;
border-radius: var(--radius-sm);
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
font-size: 14px;
font-weight: 500;
transition: var(--transition-default);
color: var(--text-primary);
box-shadow: var(--glass-shadow);
}
.btn-new-chat:hover {
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
color: var(--brand-accent);
border-color: var(--border-focus);
}
.sidebar-history {
flex: 1;
overflow-y: auto;
padding: 10px 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 4px 8px;
margin-bottom: 8px;
border-bottom: 1px solid var(--border-light);
}
.history-title-text {
font-size: 11px;
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-clear-all {
font-size: 11px;
font-weight: 500;
color: var(--text-tertiary);
cursor: pointer;
transition: var(--transition-default);
}
.btn-clear-all:hover {
color: var(--brand-danger);
}
.history-item {
padding: 10px 14px;
border-radius: var(--radius-sm);
font-size: 14px;
color: var(--text-secondary);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: var(--transition-default);
border: 1px solid transparent;
}
.history-item:hover {
background: rgba(255, 255, 255, 0.6);
color: var(--text-primary);
}
.history-item.active {
background: rgba(227, 236, 250, 0.8);
color: #001d35;
font-weight: 500;
border-color: rgba(219, 234, 254, 0.5);
}
.history-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.btn-delete-chat {
background: none;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: 4px;
display: none;
align-items: center;
justify-content: center;
border-radius: 6px;
}
.history-item:hover .btn-delete-chat {
display: flex;
}
.btn-delete-chat:hover {
color: var(--brand-danger);
background: #fce8e8;
}
.sidebar-footer {
padding: 16px;
border-top: 1px solid var(--glass-border);
display: flex;
align-items: center;
gap: 10px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
flex-wrap: wrap;
}
.user-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--brand-accent);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 600;
flex-shrink: 0;
}
.user-info-box {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
}
.user-name {
font-size: 14px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
line-height: 1.2;
}
.user-sub {
font-size: 11.5px;
font-weight: 500;
color: var(--text-secondary);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
cursor: pointer;
}
.btn-login-register {
background: var(--brand-accent);
color: #fff;
border-radius: 20px;
padding: 6px 14px;
font-size: 11.5px;
font-weight: 600;
cursor: pointer;
transition: var(--transition-default);
letter-spacing: -0.2px;
white-space: nowrap;
margin-left: auto;
box-shadow: 0 2px 8px rgba(11, 87, 208, 0.15);
}
.btn-login-register:hover {
background: #0842a0;
transform: scale(1.02);
}
.btn-power {
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
transition: var(--transition-default);
}
.btn-power:hover {
background: #fce8e8;
color: var(--brand-danger);
}
/* मेन एरिया स्टाइल्स */
.main-area {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
transition: 0.3s;
height: 100%;
overflow: hidden;
z-index: 1;
}
.chat-container {
flex: 1;
overflow-x: hidden;
overflow-y: auto;
padding: 60px 20px 180px;
display: flex;
flex-direction: column;
align-items: center;
scroll-behavior: smooth;
}
.welcome-center {
margin: auto;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
animation: fadeInUp 0.6s ease;
width: 100%;
justify-content: center;
flex: 1;
}
.welcome-center img {
width: 64px;
height: 64px;
border-radius: 18px;
margin-bottom: 24px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
object-fit: cover;
}
.welcome-center h1 {
font-size: 32px;
font-weight: 400;
color: #1f1f1f;
margin-bottom: 8px;
line-height: 1.3;
}
.welcome-center p {
font-size: 16px;
color: var(--text-secondary);
display: none;
}
#chatMessages {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.message-wrapper {
width: 100%;
max-width: 850px;
margin-bottom: 32px;
display: flex;
flex-direction: column;
animation: fadeInUp 0.3s ease;
}
.user-message {
align-self: flex-end;
max-width: 85%;
background: var(--bg-user-msg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 14px 20px;
border-radius: 24px 24px 6px 24px;
font-size: 15.5px;
line-height: 1.6;
color: var(--text-primary);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
word-wrap: break-word;
overflow-wrap: break-word;
border: 1px solid var(--glass-border);
}
.bot-message {
align-self: flex-start;
max-width: 100%;
display: flex;
gap: 16px;
width: 100%;
box-sizing: border-box;
background: transparent;
padding: 0;
border: none;
box-shadow: none;
margin-top: 4px;
}
.bot-avatar {
width: 34px;
height: 34px;
border-radius: 50%;
flex-shrink: 0;
overflow: hidden;
border: 1px solid var(--border-light);
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
margin-top: 2px;
display: flex;
align-items: center;
justify-content: center;
}
.bot-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.bot-content-wrapper {
flex: 1 1 0%;
min-width: 0;
max-width: calc(100% - 50px);
width: 100%;
overflow: hidden;
}
.bot-content {
font-size: 15.5px;
line-height: 1.7;
color: var(--text-primary);
word-wrap: break-word;
overflow-wrap: break-word;
word-break: normal;
white-space: normal;
width: 100%;
max-width: 100%;
min-height: 20px;
}
.bot-content * {
max-width: 100%;
}
.bot-content p {
margin-bottom: 16px;
white-space: normal;
}
.bot-content p:last-child {
margin-bottom: 0;
}
.bot-content strong {
font-weight: 600;
color: #000;
}
.bot-content em {
font-style: italic;
}
/* मार्कडाउन लिस्ट्स */
.bot-content ul, .bot-content ol {
padding-left: 24px;
margin-bottom: 16px;
}
.bot-content li {
margin-bottom: 8px;
}
.bot-content li:last-child {
margin-bottom: 0;
}
.bot-content code {
font-family: var(--font-code);
background: rgba(31, 31, 31, 0.05);
padding: 3px 6px;
border-radius: 6px;
font-size: 0.9em;
color: #0284c7;
white-space: pre-wrap;
word-break: break-word;
}
.bot-content pre {
background: #0d1117;
padding: 40px 16px 16px;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 16px 0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
position: relative;
white-space: pre;
word-break: normal;
}
.bot-content pre::before {
content: '';
position: absolute;
top: 14px;
left: 16px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #ff5f56;
box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
}
.bot-content pre code {
background: none;
padding: 0;
color: #e2e8f0;
font-size: 13.5px;
text-shadow: none;
white-space: pre;
}
.bot-content .mermaid {
background: #f5f7f9;
padding: 16px;
border-radius: var(--radius-md);
margin: 16px 0;
text-align: center;
overflow-x: auto;
}
.bot-content table {
display: block;
width: 100%;
overflow-x: auto;
border-collapse: collapse;
margin: 16px 0;
white-space: normal;
-webkit-overflow-scrolling: touch;
border: 1px solid var(--border-light);
border-radius: 8px;
}
.bot-content th,
.bot-content td {
border: 1px solid var(--border-light);
padding: 10px 14px;
text-align: left;
background: rgba(255, 255, 255, 0.4);
}
.bot-content th {
background: rgba(240, 244, 249, 0.7);
font-weight: 600;
}
.mjx-container {
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
}
/* अटैचमेंट कंटेनर */
.chat-attachment-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
}
.chat-file-pill {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--glass-bg);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid var(--glass-border);
padding: 8px 14px;
border-radius: var(--radius-lg);
font-size: 13px;
font-weight: 500;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
}
.chat-img-preview {
max-width: 120px;
max-height: 120px;
border-radius: 12px;
border: 1px solid var(--border-light);
object-fit: cover;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
/* थिंकिंग बॉक्स स्टाइल */
.qwen-think-box {
background: var(--glass-bg);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 16px;
margin-bottom: 16px;
overflow: hidden;
transition: var(--transition-default);
}
.qwen-think-box summary {
padding: 12px 16px;
font-size: 13.5px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
user-select: none;
list-style: none;
display: flex;
align-items: center;
gap: 8px;
background: rgba(248, 250, 252, 0.4);
}
.qwen-think-box summary::-webkit-details-marker {
display: none;
}
.qwen-think-box summary svg.arrow {
width: 14px;
height: 14px;
transition: transform var(--transition-default);
}
.qwen-think-box[open] summary svg.arrow {
transform: rotate(90deg);
}
.qwen-think-content {
padding: 16px;
border-top: 1px solid var(--border-light);
font-size: 13.5px;
color: var(--text-secondary);
font-style: italic;
line-height: 1.6;
opacity: 0.8;
white-space: pre-wrap;
}
.action-status {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
background: var(--glass-bg);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-radius: 16px;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 16px;
border: 1px solid var(--glass-border);
cursor: default;
user-select: none;
}
.action-status.active {
background: rgba(227, 236, 250, 0.8);
border-color: rgba(168, 199, 250, 0.5);
color: var(--brand-accent);
}
.flicker-text {
animation: pulseOpacity 1.2s infinite alternate;
}
.spin-icon {
animation: spin 1s linear infinite;
}
.typing-indicator {
display: inline-flex;
gap: 4px;
padding: 8px 0;
align-items: center;
}
.typing-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--text-tertiary);
animation: typingBounce 1.2s infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 0.15s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.3s;
}
.msg-actions {
display: flex;
gap: 8px;
margin-top: 14px;
opacity: 0;
transition: opacity var(--transition-default);
align-items: center;
flex-wrap: wrap;
}
.message-wrapper:hover .msg-actions {
opacity: 1;
}
@media (hover: none) {
.msg-actions {
opacity: 1;
}
}
.action-btn {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
padding: 8px 14px;
border-radius: 12px;
transition: var(--transition-default);
background: var(--glass-bg);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid var(--glass-border);
}
.action-btn:hover {
background: rgba(255, 255, 255, 0.9);
color: var(--text-primary);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}
.action-btn.listen-btn {
background: rgba(227, 236, 250, 0.8);
color: #0b57d0;
border-color: transparent;
}
.action-btn.listen-btn:hover {
background: rgba(211, 227, 253, 0.9);
}
/* बॉटम पर स्क्रॉल करने का बटन */
.btn-scroll-bottom {
position: absolute;
bottom: 110px;
left: 50%;
transform: translateX(-50%) translateY(20px);
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--glass-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
z-index: 45;
}
.btn-scroll-bottom.visible {
opacity: 1;
pointer-events: auto;
transform: translateX(-50%) translateY(0);
}
.btn-scroll-bottom:hover {
background: rgba(255, 255, 255, 0.9);
color: var(--brand-accent);
}
/* इनपुट एरिया */
.input-dock {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 10px 20px 24px;
padding-bottom: max(24px, env(safe-area-inset-bottom));
background: linear-gradient(to top, rgba(17, 25, 40, 0.18) 0%, transparent 100%);
display: flex;
flex-direction: column;
align-items: center;
z-index: 50;
}
.loc-toast {
display: none;
align-items: center;
gap: 6px;
font-size: 12.5px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 12px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 6px 14px;
border-radius: 16px;
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
}
.input-container {
width: 100%;
max-width: 850px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(168, 199, 250, 0.15);
border-radius: 50px;
padding: 8px 12px 8px 16px;
display: flex;
flex-direction: column;
transition: var(--transition-default);
}
.input-container:focus-within {
box-shadow: 0 8px 24px rgba(11, 87, 208, 0.15), 0 0 0 2px rgba(168, 199, 250, 0.5);
border-color: rgba(168, 199, 250, 0.6);
background: rgba(255, 255, 255, 0.92);
}
.file-preview-bar {
display: none;
padding: 4px 0 12px;
align-items: center;
gap: 10px;
margin-bottom: 4px;
}
.file-preview-bar.active {
display: flex;
}
.file-preview-pill {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid var(--border-light);
padding: 6px 12px;
border-radius: 16px;
font-size: 13.5px;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
}
.file-preview-icon {
width: 18px;
height: 18px;
object-fit: cover;
border-radius: 4px;
}
.file-preview-name {
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
}
.btn-remove-file {
width: 20px;
height: 20px;
border-radius: 50%;
background: #fce8e8;
color: var(--brand-danger);
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition-default);
margin-left: 4px;
}
.btn-remove-file:hover {
background: #fca5a5;
color: #fff;
}
.input-row {
display: flex;
align-items: flex-end;
gap: 10px;
width: 100%;
align-items: center;
}
.tools-left {
display: flex;
gap: 4px;
align-items: center;
position: relative;
}
.tool-btn {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
transition: all 0.2s ease;
position: relative;
flex-shrink: 0;
}
.tool-btn:hover {
background: rgba(255, 255, 255, 0.7);
color: var(--text-primary);
transform: scale(1.05);
}
.tool-btn.active-env {
color: #fff;
background: rgba(11, 87, 208, 0.7);
}
.tool-btn.active-think-low {
color: #fff;
background: rgba(179, 38, 30, 0.7);
}
.tool-btn.active-think-medium {
color: #fff;
background: rgba(227, 116, 0, 0.7);
}
.tool-btn.active-think-high {
color: #fff;
background: rgba(11, 87, 208, 0.7);
}
.tool-btn.active-search {
color: #fff;
background: rgba(20, 108, 46, 0.7);
}
.btn-mic.recording {
color: var(--brand-danger);
background: rgba(252, 232, 232, 0.9);
animation: recordPulse 1.5s infinite;
}
.chat-input {
flex: 1;
padding: 12px 4px;
font-size: 15.5px;
resize: none;
max-height: 180px;
min-height: 24px;
color: var(--text-primary);
line-height: 1.5;
background: transparent;
caret-color: var(--brand-accent);
}
.chat-input::placeholder {
color: var(--text-tertiary);
font-weight: 400;
}
.btn-send {
width: 44px;
height: 44px;
border-radius: 50%;
background: transparent;
color: var(--text-primary);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
flex-shrink: 0;
}
.btn-send:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.8);
transform: scale(1.08);
}
.btn-send:disabled {
color: var(--text-tertiary);
cursor: not-allowed;
background: transparent;
}
.btn-stop {
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(252, 232, 232, 0.9);
color: var(--brand-danger);
display: none;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
flex-shrink: 0;
}
.btn-stop.active {
display: flex;
}
.btn-stop:hover {
background: #fca5a5;
color: #fff;
}
.hidden-input {
display: none;
}
/* ड्रॉपडाउन मेनू */
.dropdown-menu {
position: absolute;
bottom: calc(100% + 14px);
left: 0;
background: var(--glass-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 8px;
box-shadow: var(--glass-shadow);
display: none;
flex-direction: column;
min-width: 200px;
z-index: 100;
transform-origin: bottom left;
}
.dropdown-menu.active {
display: flex;
animation: dropUpFade 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.dropdown-header {
padding: 8px 14px 12px;
font-size: 11.5px;
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--border-light);
margin-bottom: 4px;
}
.dropdown-item {
padding: 12px 14px;
border-radius: 12px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
cursor: pointer;
transition: var(--transition-default);
color: var(--text-secondary);
}
.dropdown-item:hover {
background: rgba(255, 255, 255, 0.6);
color: var(--text-primary);
}
.dropdown-item.selected {
background: rgba(227, 236, 250, 0.8);
color: var(--brand-accent);
font-weight: 600;
}
.dropdown-item svg {
color: inherit;
}
/* ऑथेंटिकेशन (लॉगिन/रजिस्टर) मोडल */
.auth-overlay {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.5);
backdrop-filter: blur(8px);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.auth-modal {
background: var(--glass-bg);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
width: 90%;
max-width: 380px;
border-radius: var(--radius-lg);
padding: 32px;
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);
position: relative;
border: 1px solid var(--glass-border);
animation: modalFadeIn 0.2s ease;
}
.auth-close {
position: absolute;
top: 16px;
right: 16px;
color: var(--text-tertiary);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: var(--transition-default);
}
.auth-close:hover {
background: var(--bg-sidebar);
color: var(--text-primary);
}
.auth-title {
font-size: 22px;
font-weight: 600;
margin-bottom: 24px;
text-align: center;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.auth-tabs {
display: flex;
background: rgba(240, 244, 249, 0.6);
border-radius: var(--radius-sm);
padding: 4px;
margin-bottom: 20px;
}
.auth-tab {
flex: 1;
text-align: center;
padding: 10px;
font-size: 13.5px;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
color: var(--text-secondary);
transition: var(--transition-default);
}
.auth-tab.active {
background: rgba(255, 255, 255, 0.9);
color: var(--text-primary);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.auth-input {
width: 100%;
padding: 14px 16px;
border: 1px solid var(--border-light);
border-radius: var(--radius-sm);
font-size: 14.5px;
margin-bottom: 12px;
transition: var(--transition-default);
background: rgba(248, 250, 252, 0.8);
caret-color: var(--brand-accent);
}
.auth-input:focus {
border-color: var(--brand-accent);
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.auth-btn {
width: 100%;
padding: 14px;
background: var(--brand-color);
color: #fff;
font-size: 14.5px;
font-weight: 500;
border-radius: var(--radius-sm);
transition: var(--transition-default);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
}
.auth-btn:hover {
background: #1e293b;
transform: translateY(-1px);
}
.auth-phase {
display: none;
}
.auth-phase.active {
display: block;
}
.auth-message {
font-size: 13px;
text-align: center;
margin-top: 14px;
min-height: 18px;
font-weight: 500;
}
.mobile-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.3);
backdrop-filter: blur(3px);
z-index: 95;
opacity: 0;
transition: 0.3s;
pointer-events: none;
}
.mobile-overlay.active {
display: block;
opacity: 1;
pointer-events: all;
}
/* कस्टम कन्फर्म बॉक्स */
.custom-confirm-overlay {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.4);
backdrop-filter: blur(6px);
display: none;
align-items: center;
justify-content: center;
z-index: 2500;
}
.custom-confirm-box {
background: var(--glass-bg);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--glass-border);
border-radius: 28px;
padding: 28px;
max-width: 340px;
width: 90%;
text-align: center;
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.18);
animation: modalFadeIn 0.2s ease;
}
.custom-confirm-msg {
font-size: 16px;
font-weight: 500;
margin-bottom: 24px;
color: var(--text-primary);
line-height: 1.5;
}
.custom-confirm-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.custom-confirm-btn {
flex: 1;
padding: 12px;
border-radius: 16px;
font-size: 14px;
font-weight: 600;
transition: var(--transition-default);
}
.custom-confirm-btn.cancel {
background: rgba(240, 244, 249, 0.8);
color: var(--text-secondary);
}
.custom-confirm-btn.confirm {
background: var(--brand-danger);
color: #fff;
}
.custom-confirm-btn.cancel:hover {
background: rgba(220, 230, 240, 0.9);
}
.custom-confirm-btn.confirm:hover {
background: #a01e18;
}
/* रिस्पॉन्सिव मीडिया क्वेरीज़ */
@media (max-width: 900px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
height: 100%;
box-shadow: 4px 0 32px rgba(0, 0, 0, 0.1);
}
.sidebar.collapsed {
transform: translateX(-100%);
box-shadow: none;
}
.input-dock {
padding: 10px 10px 20px;
}
.input-container {
border-radius: 28px;
padding: 8px 10px 8px 16px;
}
.tool-btn {
width: 38px;
height: 38px;
}
.btn-send, .btn-stop {
width: 40px;
height: 40px;
}
.chat-container {
padding: 60px 10px 160px;
}
.user-message {
font-size: 14.5px;
max-width: 90%;
}
.bot-content {
font-size: 14.5px;
}
.welcome-center h1 {
font-size: 28px;
}
}
@media (max-width: 600px) {
.chat-container {
padding: 50px 8px 140px;
}
.input-container {
padding: 6px 8px 6px 12px;
border-radius: 24px;
}
.tool-btn {
width: 34px;
height: 34px;
}
.btn-send, .btn-stop {
width: 36px;
height: 36px;
}
.chat-input {
font-size: 14px;
}
.user-message {
max-width: 92%;
font-size: 14px;
}
.bot-content {
font-size: 14px;
}
.welcome-center h1 {
font-size: 26px;
}
}
</style>
</head>
<body>
<!-- मोबाइल मेनू बटन -->
<button class="btn-menu" id="btnMenuOpen" onclick="UI.toggleSidebar()" title="Menu">
<svg style="width:24px;height:24px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
<line x1="4" y1="9" x2="20" y2="9"></line>
<line x1="4" y1="15" x2="14" y2="15"></line>
</svg>
</button>
<!-- मोबाइल ओवरले -->
<div class="mobile-overlay" id="mobileOverlay" onclick="UI.toggleSidebar()"></div>
<!-- साइडबार -->
<aside class="sidebar collapsed" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">CODE VED</div>
<button class="btn-menu" style="position:static; width:36px; height:36px;" onclick="UI.toggleSidebar()">
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<button class="btn-new-chat" onclick="HistoryManager.startNew()">
<svg style="width:18px; height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
New Chat
</button>
<div class="sidebar-history" id="chatHistory"></div>
<div class="sidebar-footer">
<div class="user-avatar" id="uAv">G</div>
<div class="user-info-box">
<div class="user-name" id="uName">Guest Session</div>
<div class="user-sub" id="uSub" style="cursor:pointer;">Queries: 0/10</div>
</div>
<button class="btn-login-register" id="btnLoginRegister" onclick="Auth.openModal()">Login/Register</button>
<button class="btn-power" id="btnDeleteAcc" onclick="Auth.handleDeleteAccount()" style="display:none; color:var(--brand-danger);" title="Delete Account">
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path>
</svg>
</button>
<button class="btn-power" id="btnLogout" onclick="Auth.handleLogout()" style="display:none;" title="Logout">
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path>
<line x1="12" y1="2" x2="12" y2="12"></line>
</svg>
</button>
</div>
</aside>
<!-- मुख्य चैट एरिया -->
<main class="main-area">
<div class="chat-container" id="chatContainer">
<div class="welcome-center" id="welcomeScreen">
<img src="https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png" alt="CODE VED Logo">
<h1 id="welcomeTitle">Hello, Guest!<br>How can I help you today?</h1>
<p id="welcomeGreeting" style="display: none;">Engineered by Divy Patel</p>
</div>
<div id="chatMessages"></div>
</div>
<button id="scrollToBottomBtn" class="btn-scroll-bottom" onclick="UI.scrollToBottom(true); UI.autoScroll = true;" title="Go to latest">
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<polyline points="19 12 12 19 5 12"></polyline>
</svg>
</button>
<!-- इनपुट डोक -->
<div class="input-dock" id="inputDock">
<div class="loc-toast" id="locStatus"></div>
<div class="input-container">
<div class="file-preview-bar" id="filePreviewBar">
<div class="file-preview-pill">
<img id="imgPreview" class="file-preview-icon" src="" style="display:none;">
<svg id="docPreview" style="display:none; width:16px; height:16px; color:var(--text-secondary);" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<div class="file-preview-name" id="fileName">document.pdf</div>
<button class="btn-remove-file" onclick="FileSys.discard()">
<svg style="width:12px;height:12px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<div class="input-row">
<div class="tools-left">
<div style="position: relative;">
<button class="tool-btn" onclick="UI.toggleAttachMenu()" title="Attach File">
<svg style="width:24px;height:24px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<div class="dropdown-menu" id="attachMenu">
<div class="dropdown-header">Attachments</div>
<div class="dropdown-item" onclick="document.getElementById('imgUpload').click()">
<span style="display:flex;align-items:center;gap:10px;">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg> Upload Image
</span>
</div>
<div class="dropdown-item" onclick="document.getElementById('docUpload').click()">
<span style="display:flex;align-items:center;gap:10px;">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg> Upload Document
</span>
</div>
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
<div class="dropdown-header">Tools</div>
<div class="dropdown-item" onclick="EnvironmentManager.toggle()">
<span style="display:flex;align-items:center;gap:10px;">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg> GPS & Weather
</span>
</div>
<div class="dropdown-item" onclick="ThinkingManager.toggleMenu()">
<span style="display:flex;align-items:center;gap:10px;">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a5 5 0 0 0-5 5v2a5 5 0 0 0 10 0V7a5 5 0 0 0-5-5z"></path>
<path d="M8 14H6a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2"></path>
</svg> Thinking Mode
</span>
</div>
<div class="dropdown-item" onclick="TTSManager.toggleMenu()" style="color:#0b57d0;">
<span style="display:flex;align-items:center;gap:10px;">
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
</svg> Select Voice Mode
</span>
</div>
</div>
<div class="dropdown-menu" id="voiceMenu" style="min-width: 220px; left: 45px; bottom: 40px; max-height: 300px; overflow-y: auto;">
<div class="dropdown-header">Premium Voices</div>
<div id="voiceOptionsList"></div>
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
<div class="dropdown-header">Language</div>
<div id="languageOptionsList"></div>
</div>
<div class="dropdown-menu" id="thinkMenu" style="min-width: 180px; left: 45px; bottom: 40px;">
<div class="dropdown-header">Reasoning Effort</div>
<div class="dropdown-item" onclick="ThinkingManager.setEffort('low')">⚡ Low (Fast)</div>
<div class="dropdown-item" onclick="ThinkingManager.setEffort('medium')">🧠 Medium (Balanced)</div>
<div class="dropdown-item" onclick="ThinkingManager.setEffort('high')">🔍 High (Deep logic)</div>
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
<div class="dropdown-item" style="color:var(--brand-danger);" onclick="ThinkingManager.disable()">❌ Disable Thinking</div>
</div>
<input type="file" id="imgUpload" class="hidden-input" accept="image/*" onchange="FileSys.process(this, 'image')">
<input type="file" id="docUpload" class="hidden-input" accept=".pdf,.txt,.docx,.html,.js,.py,.css,.cpp,.c,.json,.md" onchange="FileSys.process(this, 'document')">
</div>
<button class="tool-btn" id="btnSearch" onclick="SearchManager.toggle()" title="Web Search: OFF">
<svg style="width:22px;height:22px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
<textarea class="chat-input" id="mainInput" placeholder="Ask CODE VED..." rows="1"></textarea>
<button class="tool-btn btn-mic" id="btnStt" onclick="Speech.toggle()" title="Voice Typing">
<svg style="width:22px;height:22px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" y1="19" x2="12" y2="23"></line>
<line x1="8" y1="23" x2="16" y2="23"></line>
</svg>
</button>
<button class="btn-stop" id="btnStop" onclick="Chat.stopGeneration()">
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="currentColor">
<rect x="6" y="6" width="12" height="12" rx="2"></rect>
</svg>
</button>
<button class="btn-send" id="btnSend" onclick="Chat.handleSend()">
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
</div>
</div>
</div>
</main>
<!-- ऑथेंटिकेशन मोडल -->
<div class="auth-overlay" id="authModal" onclick="if(event.target===this) Auth.closeModal()">
<div class="auth-modal">
<button class="auth-close" onclick="Auth.closeModal()">
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div class="auth-title">System Access</div>
<div class="auth-tabs">
<div class="auth-tab active" id="tabLogin" onclick="Auth.switchTab('login')">Login</div>
<div class="auth-tab" id="tabRegister" onclick="Auth.switchTab('register')">Register</div>
</div>
<div id="flowLogin">
<div class="auth-phase active" id="loginPhase1">
<input type="email" class="auth-input" id="logEmail" placeholder="Email Address">
<button class="auth-btn" id="btnLogOtp" onclick="Auth.process('login_send_otp')">Continue</button>
</div>
<div class="auth-phase" id="loginPhase2">
<input type="number" class="auth-input" id="logOtp" placeholder="Enter OTP">
<button class="auth-btn" id="btnLogVerify" onclick="Auth.process('login_verify')">Verify & Access</button>
<button style="margin-top:12px; font-size:12px; color:var(--text-secondary); width:100%; text-align:center; background:none; border:none; cursor:pointer;" onclick="Auth.switchPhase('loginPhase2', 'loginPhase1')">Back</button>
</div>
</div>
<div id="flowRegister" style="display:none;">
<div class="auth-phase active" id="regPhase1">
<input type="text" class="auth-input" id="regName" placeholder="Full Name">
<input type="email" class="auth-input" id="regEmail" placeholder="Email Address">
<button class="auth-btn" id="btnRegOtp" onclick="Auth.process('register_send_otp')">Create Account</button>
</div>
<div class="auth-phase" id="regPhase2">
<input type="number" class="auth-input" id="regOtp" placeholder="Enter OTP">
<button class="auth-btn" id="btnRegVerify" onclick="Auth.process('register_verify')">Verify & Register</button>
</div>
</div>
<div class="auth-message" id="authMsg"></div>
</div>
</div>
<!-- कस्टम कन्फर्म बॉक्स -->
<div class="custom-confirm-overlay" id="customConfirmOverlay">
<div class="custom-confirm-box">
<div class="custom-confirm-msg" id="customConfirmMsg"></div>
<div class="custom-confirm-btns">
<button class="custom-confirm-btn cancel" id="customConfirmCancel">Cancel</button>
<button class="custom-confirm-btn confirm" id="customConfirmOk">Confirm</button>
</div>
</div>
</div>
<!-- मुख्य जावास्क्रिप्ट लॉजिक -->
<script>
(function() {
// कॉन्फ़िगरेशन
const Config = {
GAS_URL: "https://script.google.com/macros/s/AKfycbzNO3inVc33ImhfLyde-JjjK9ZlPckLBksqCnCzelfhcklX6mp8KW8vfPTW4oWJTCcN/exec",
API_ENDPOINT: "/api/chat",
LOGO: "https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png"
};
// ग्लोबल स्टेट मैनेजमेंट
const State = {
user: localStorage.getItem('codeved_user') || null,
name: localStorage.getItem('codeved_name') || null,
guestCount: (() => {
const v = parseInt(localStorage.getItem('codeved_guest') || '0', 10);
return isNaN(v) ? 0 : v;
})(),
attachment: null,
isProcessing: false,
history: [],
currentThreadId: null,
currentTitle: null,
abortController: null,
location: null,
weatherContext: null,
thinkingMode: false,
thinkingEffort: "medium",
searchEnabled: false,
lastUserMessage: null
};
// PDF.js वर्कर सेटअप
if (typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
}
// कस्टम कन्फर्म बॉक्स फ़ंक्शन
function customConfirm(message) {
return new Promise((resolve) => {
const overlay = document.getElementById('customConfirmOverlay');
const msgEl = document.getElementById('customConfirmMsg');
const cancelBtn = document.getElementById('customConfirmCancel');
const okBtn = document.getElementById('customConfirmOk');
msgEl.textContent = message;
overlay.style.display = 'flex';
function cleanup() {
overlay.style.display = 'none';
cancelBtn.removeEventListener('click', onCancel);
okBtn.removeEventListener('click', onOk);
}
function onCancel() { cleanup(); resolve(false); }
function onOk() { cleanup(); resolve(true); }
cancelBtn.addEventListener('click', onCancel);
okBtn.addEventListener('click', onOk);
});
}
// UI मैनेजमेंट
const UI = {
autoScroll: true,
toggleSidebar() {
const sb = document.getElementById('sidebar');
const overlay = document.getElementById('mobileOverlay');
sb.classList.toggle('collapsed');
overlay.classList.toggle('active', !sb.classList.contains('collapsed') && window.innerWidth <= 900);
},
toggleAttachMenu() {
document.getElementById('attachMenu').classList.toggle('active');
document.getElementById('thinkMenu').classList.remove('active');
document.getElementById('voiceMenu').classList.remove('active');
},
autoGrow(el) {
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 180) + 'px';
},
scrollToBottom(force = false) {
const c = document.getElementById('chatContainer');
if (force || this.autoScroll) {
c.scrollTop = c.scrollHeight;
}
},
escape(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
},
showAuthMsg(msg, isError = true) {
const el = document.getElementById('authMsg');
el.innerText = msg;
el.style.color = isError ? 'var(--brand-danger)' : 'var(--brand-success)';
setTimeout(() => el.innerText = '', 4000);
},
updateUserInfo(name, email) {
if (name) {
State.name = name;
localStorage.setItem('codeved_name', name);
}
const dispName = State.name || email.split('@')[0];
document.getElementById('uAv').innerText = dispName.charAt(0).toUpperCase();
document.getElementById('uName').innerText = dispName;
document.getElementById('uSub').innerText = email;
document.getElementById('btnLogout').style.display = 'flex';
document.getElementById('btnDeleteAcc').style.display = 'flex';
document.getElementById('btnLoginRegister').style.display = 'none';
document.getElementById('welcomeTitle').innerHTML = `Hello, ${dispName}!<br>How can I help you today?`;
},
updateWelcomeScreen() {
document.getElementById('welcomeScreen').style.display = State.history.length === 0 ? 'flex' : 'none';
}
};
// स्क्रॉल इवेंट लिसनर
const chatContainerEl = document.getElementById('chatContainer');
const scrollBtnEl = document.getElementById('scrollToBottomBtn');
chatContainerEl.addEventListener('scroll', () => {
const scrollBottom = chatContainerEl.scrollHeight - chatContainerEl.scrollTop - chatContainerEl.clientHeight;
UI.autoScroll = scrollBottom <= 50;
scrollBtnEl.classList.toggle('visible', !UI.autoScroll);
});
// बाहर क्लिक करने पर मेनू बंद करें
document.addEventListener('click', (e) => {
if (!e.target.closest('.tools-left')) {
document.getElementById('attachMenu').classList.remove('active');
document.getElementById('thinkMenu').classList.remove('active');
document.getElementById('voiceMenu').classList.remove('active');
}
});
// इनपुट बॉक्स लिसनर्स
const inp = document.getElementById('mainInput');
inp.addEventListener('input', function() {
UI.autoGrow(this);
});
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
Chat.handleSend();
}
});
// ऑथेंटिकेशन लॉजिक
const Auth = {
init() {
if (State.user) {
UI.updateUserInfo(State.name, State.user);
HistoryManager.syncAllChats();
} else {
document.getElementById('uAv').innerText = "G";
document.getElementById('uName').innerText = "Guest Mode";
document.getElementById('uSub').innerText = `Queries: ${State.guestCount}/10`;
document.getElementById('btnLogout').style.display = 'none';
document.getElementById('btnDeleteAcc').style.display = 'none';
document.getElementById('btnLoginRegister').style.display = 'inline-flex';
document.getElementById('welcomeTitle').innerHTML = `Hello, Guest!<br>How can I help you today?`;
}
},
async handleLogout() {
const ok = await customConfirm("Are you sure you want to logout?");
if (ok) {
localStorage.removeItem('codeved_user');
localStorage.removeItem('codeved_name');
location.reload();
}
},
async handleDeleteAccount() {
const ok = await customConfirm("Are you sure you want to permanently delete your account and all data?");
if (ok) {
try {
await fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ action: "delete_account", email: State.user })
});
} catch(e) {
console.error("Account delete error:", e);
} finally {
localStorage.removeItem('codeved_user');
localStorage.removeItem('codeved_name');
location.reload();
}
}
},
openModal() {
document.getElementById('authModal').style.display = 'flex';
},
closeModal() {
document.getElementById('authModal').style.display = 'none';
},
switchTab(tab) {
document.getElementById('tabLogin').classList.remove('active');
document.getElementById('tabRegister').classList.remove('active');
document.getElementById('flowLogin').style.display = 'none';
document.getElementById('flowRegister').style.display = 'none';
if (tab === 'login') {
document.getElementById('tabLogin').classList.add('active');
document.getElementById('flowLogin').style.display = 'block';
} else {
document.getElementById('tabRegister').classList.add('active');
document.getElementById('flowRegister').style.display = 'block';
}
},
switchPhase(from, to) {
document.getElementById(from).classList.remove('active');
document.getElementById(to).classList.add('active');
},
async process(action) {
let payload = { action: action };
let btnId = '';
if (action === 'register_send_otp') {
payload.name = document.getElementById('regName').value.trim();
payload.email = document.getElementById('regEmail').value.trim();
payload.phone = "0000000000";
payload.organization = "CODE VED";
if (!payload.name || !payload.email) return UI.showAuthMsg("Details missing.");
btnId = 'btnRegOtp';
} else if (action === 'login_send_otp') {
payload.email = document.getElementById('logEmail').value.trim();
if (!payload.email) return UI.showAuthMsg("Email required.");
btnId = 'btnLogOtp';
} else if (action === 'register_verify' || action === 'login_verify') {
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
payload.otp = document.getElementById(action === 'register_verify' ? 'regOtp' : 'logOtp').value.trim();
if (!payload.otp) return UI.showAuthMsg("OTP required.");
btnId = action === 'register_verify' ? 'btnRegVerify' : 'btnLogVerify';
}
const btn = document.getElementById(btnId);
const originalText = btn.innerText;
btn.disabled = true;
btn.innerText = "Wait...";
try {
const res = await fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify(payload)
});
const textResponse = await res.text();
let data;
try {
data = JSON.parse(textResponse);
} catch (parseErr) {
console.error("Failed to parse GAS response:", textResponse);
UI.showAuthMsg("Invalid response from server. Network error.");
btn.disabled = false;
btn.innerText = originalText;
return;
}
if (data.status === 'success') {
UI.showAuthMsg(data.message, false);
if (action === 'register_send_otp') {
Auth.switchPhase('regPhase1', 'regPhase2');
} else if (action === 'login_send_otp') {
Auth.switchPhase('loginPhase1', 'loginPhase2');
} else {
localStorage.setItem('codeved_user', payload.email);
if (data.user && data.user.name) {
localStorage.setItem('codeved_name', data.user.name);
}
location.reload();
}
} else {
UI.showAuthMsg(data.message);
}
} catch (e) {
console.error("Auth process error:", e);
UI.showAuthMsg("Network Error. Please try again.");
}
btn.disabled = false;
btn.innerText = originalText;
}
};
// हिस्ट्री मैनेजमेंट
const HistoryManager = {
async syncAllChats() {
if (!State.user) return;
try {
const res = await fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ action: "get_all_chats", email: State.user })
});
const textResponse = await res.text();
const data = JSON.parse(textResponse);
if (data.status === 'success') {
if (data.user) {
UI.updateUserInfo(data.user.name, data.user.email);
}
this.renderSidebar(data.chats || []);
}
} catch (e) {
console.error("History sync error:", e);
}
},
renderSidebar(chats) {
const container = document.getElementById('chatHistory');
container.innerHTML = `<div class="history-header"><span class="history-title-text">Recent Workspaces</span><span class="btn-clear-all" onclick="HistoryManager.clearAll()">Clear All</span></div>`;
chats.forEach(chat => {
const isActive = State.currentThreadId === chat.threadId ? 'active' : '';
container.innerHTML += `
<div class="history-item ${isActive}" onclick="HistoryManager.loadChat('${chat.threadId}')">
<span class="history-text">${UI.escape(chat.title)}</span>
<button class="btn-delete-chat" onclick="event.stopPropagation(); HistoryManager.deleteChat('${chat.threadId}')" title="Delete">
<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</div>`;
});
},
async loadChat(threadId) {
if (!State.user || State.isProcessing) return;
State.currentThreadId = threadId;
document.getElementById('chatMessages').innerHTML = '';
try {
const res = await fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ action: "get_chat", email: State.user, threadId: threadId })
});
const textResponse = await res.text();
const data = JSON.parse(textResponse);
if (data.status === 'success') {
State.history = JSON.parse(data.historyJSON || "[]");
UI.updateWelcomeScreen();
for (const msg of State.history) {
if (msg.role === 'user') {
let dispText = msg.content;
if (dispText.includes('[SYSTEM REAL-TIME WEATHER:')) {
dispText = dispText.split('\n\n[SYSTEM REAL-TIME WEATHER:')[0];
}
if (dispText.includes('---END DATA---')) {
dispText = dispText.split('User: ')[1] || '[Attached File]';
}
Chat.renderUser(dispText);
} else {
const msgId = 'hist_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
const botObj = Chat.renderBot(msgId);
Chat.parseAndRender(msg.content, false, false, botObj.contentDiv);
TTSManager.autoPrepare(msgId, msg.content, botObj.listenBtn);
}
}
UI.autoScroll = true;
UI.scrollToBottom();
if (window.innerWidth <= 900) UI.toggleSidebar();
}
} catch (e) {
console.error("Load chat error:", e);
}
},
startNew() {
if (State.isProcessing) return;
State.currentThreadId = null;
State.currentTitle = null;
State.history = [];
document.getElementById('chatMessages').innerHTML = '';
UI.updateWelcomeScreen();
UI.autoScroll = true;
UI.scrollToBottom();
if (window.innerWidth <= 900) UI.toggleSidebar();
},
saveCurrent() {
if (!State.user || State.history.length === 0) return;
if (!State.currentThreadId) {
State.currentThreadId = "thr_" + Date.now();
const firstUser = State.history.find(m => m.role === 'user');
State.currentTitle = firstUser ? firstUser.content.substring(0, 25) : "New Workspace";
}
fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({
action: "save_chat",
email: State.user,
threadId: State.currentThreadId,
title: State.currentTitle,
historyJSON: JSON.stringify(State.history)
})
}).catch(e => console.error("Save error:", e)).then(() => this.syncAllChats());
},
async clearAll() {
const ok = await customConfirm("Clear all workspaces?");
if (ok) {
State.history = [];
State.currentThreadId = null;
State.currentTitle = null;
document.getElementById('chatMessages').innerHTML = '';
UI.updateWelcomeScreen();
UI.autoScroll = true;
UI.scrollToBottom();
fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ action: "clear_all_chats", email: State.user })
}).catch(e => console.error("Clear error:", e)).then(() => this.syncAllChats());
}
},
async deleteChat(threadId) {
const ok = await customConfirm("Delete this workspace?");
if (ok) {
if (State.currentThreadId === threadId) this.startNew();
fetch(Config.GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ action: "delete_chat", email: State.user, threadId: threadId })
}).catch(e => console.error("Delete error:", e)).then(() => this.syncAllChats());
}
}
};
const EnvironmentManager = {
toggle() {
const status = document.getElementById('locStatus');
document.getElementById('attachMenu').classList.remove('active');
if (State.location) {
State.location = null;
State.weatherContext = null;
status.style.display = 'none';
} else {
if (navigator.geolocation) {
status.style.display = 'flex';
status.innerHTML = `<span class="flicker-text">Finding location...</span>`;
navigator.geolocation.getCurrentPosition(pos => {
State.location = { lat: pos.coords.latitude, lng: pos.coords.longitude };
status.innerHTML = `<span style="color:var(--brand-success);">✓ Location active!</span>`;
setTimeout(() => status.style.display = 'none', 3000);
}, err => {
status.innerHTML = `<span style="color:var(--brand-danger);">Location denied.</span>`;
setTimeout(() => status.style.display = 'none', 3000);
});
}
}
}
};
const SearchManager = {
toggle() {
State.searchEnabled = !State.searchEnabled;
const btn = document.getElementById('btnSearch');
btn.classList.toggle('active-search', State.searchEnabled);
btn.title = State.searchEnabled ? 'Web Search: ON' : 'Web Search: OFF';
}
};
const ThinkingManager = {
toggleMenu() {
document.getElementById('thinkMenu').classList.toggle('active');
document.getElementById('attachMenu').classList.remove('active');
},
setEffort(level) {
State.thinkingMode = true;
State.thinkingEffort = level;
document.getElementById('thinkMenu').classList.remove('active');
ThinkingManager.updateIndicator();
},
disable() {
State.thinkingMode = false;
document.getElementById('thinkMenu').classList.remove('active');
ThinkingManager.updateIndicator();
},
updateIndicator() {
const btn = document.getElementById('btnSearch');
btn.classList.remove('active-think-low', 'active-think-medium', 'active-think-high');
if (State.thinkingMode) {
btn.classList.add(`active-think-${State.thinkingEffort}`);
}
}
};
const FileSys = {
async process(input, type) {
document.getElementById('attachMenu').classList.remove('active');
const file = input.files[0];
if (!file) return;
document.getElementById('filePreviewBar').classList.add('active');
document.getElementById('fileName').innerText = file.name;
if (type === 'image') {
document.getElementById('imgPreview').style.display = 'block';
document.getElementById('docPreview').style.display = 'none';
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let w = img.width, h = img.height;
const max = 1024;
if(w > max || h > max) {
if(w > h) {
h = (max/w)*h; w = max;
} else {
w = (max/h)*w; h = max;
}
}
canvas.width = w;
canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);
const b64 = canvas.toDataURL('image/jpeg', 0.8).split(',')[1];
State.attachment = { type: 'image', data: b64, name: file.name };
document.getElementById('imgPreview').src = `data:image/jpeg;base64,${b64}`;
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
} else {
document.getElementById('imgPreview').style.display = 'none';
document.getElementById('docPreview').style.display = 'block';
if (file.name.endsWith('.pdf')) {
try {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
let text = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
text += content.items.map(item => item.str).join(' ') + '\n';
}
State.attachment = { type: 'text', data: text, name: file.name };
} catch(e) {
State.attachment = { type: 'text', data: '[PDF parsing error]', name: file.name };
}
} else if (file.name.endsWith('.docx')) {
try {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
State.attachment = { type: 'text', data: result.value, name: file.name };
} catch(e) {
State.attachment = { type: 'text', data: '[DOCX parsing error]', name: file.name };
}
} else {
const reader = new FileReader();
reader.onload = (e) => {
State.attachment = { type: 'text', data: e.target.result, name: file.name };
};
reader.readAsText(file);
}
}
input.value = '';
},
discard() {
State.attachment = null;
document.getElementById('filePreviewBar').classList.remove('active');
}
};
const Speech = {
rec: null,
isRec: false,
init() {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) return;
this.rec = new SR();
this.rec.continuous = false;
this.rec.interimResults = true;
this.rec.lang = navigator.language || 'en-US';
this.rec.onstart = () => {
this.isRec = true;
document.getElementById('btnStt').classList.add('recording');
};
this.rec.onresult = (e) => {
if (!e.results || !e.results.length) return;
let trans = '';
for (let i = e.resultIndex; i < e.results.length; ++i) {
if (e.results[i].isFinal && e.results[i][0]) {
trans += e.results[i][0].transcript;
}
}
if (trans) {
const input = document.getElementById('mainInput');
input.value += (input.value ? ' ' : '') + trans;
UI.autoGrow(input);
}
};
this.rec.onerror = () => this.stop();
this.rec.onend = () => this.stop();
},
toggle() {
if (!this.rec) this.init();
if (this.isRec) {
this.stop();
} else {
try {
this.rec.start();
} catch(e) {}
}
},
stop() {
if(this.rec) this.rec.stop();
this.isRec = false;
document.getElementById('btnStt').classList.remove('recording');
}
};
const renderer = {
code(code, lang) {
const language = (lang || '').match(/\S*/)?.[0];
if (language === 'mermaid') {
return `<pre class="mermaid">${code}</pre>`;
}
if (language && hljs.getLanguage(language)) {
try {
return `<pre><code class="hljs ${language}">${hljs.highlight(code, { language }).value}</code></pre>`;
} catch (__) {}
}
return `<pre><code class="hljs">${hljs.highlightAuto(code).value}</code></pre>`;
}
};
marked.use({ renderer, gfm: true, breaks: true, pedantic: false });
function initMermaid() {
if (typeof mermaid !== 'undefined') {
mermaid.initialize({ startOnLoad: false, theme: 'default' });
}
}
function renderMermaid(container) {
if (typeof mermaid === 'undefined') return;
const diagrams = container.querySelectorAll('.mermaid');
if (diagrams.length === 0) return;
try {
mermaid.run({ nodes: diagrams });
} catch (e) {
console.warn('Mermaid render error:', e);
}
}
function runRendering(container, isProcessing) {
if (window.MathJax && !isProcessing) {
try {
MathJax.typesetClear([container]);
MathJax.typesetPromise([container]).catch(console.error);
} catch (e) {}
}
renderMermaid(container);
}
const TTSManager = {
cache: {},
currentAudio: null,
selectedVoice: "M2",
selectedLanguage: "English",
isPlaying: false,
currentMsgId: null,
currentBtnElement: null,
preparingSet: new Set(),
voiceMap: {
"M1": "Aarav", "M2": "Kabir", "M3": "Vihaan", "M4": "Advik", "M5": "Rohan",
"F1": "Priya", "F2": "Ananya", "F3": "Diya", "F4": "Sneha", "F5": "Kavya"
},
languageMap: {
"English": "en", "Korean": "ko", "Japanese": "ja", "Arabic": "ar", "Bulgarian": "bg",
"Czech": "cs", "Danish": "da", "German": "de", "Greek": "el", "Spanish": "es",
"Estonian": "et", "Finnish": "fi", "French": "fr", "Hindi": "hi", "Croatian": "hr",
"Hungarian": "hu", "Indonesian": "id", "Italian": "it", "Lithuanian": "lt", "Latvian": "lv",
"Dutch": "nl", "Polish": "pl", "Portuguese": "pt", "Romanian": "ro", "Russian": "ru",
"Slovak": "sk", "Slovenian": "sl", "Swedish": "sv", "Turkish": "tr", "Ukrainian": "uk",
"Vietnamese": "vi"
},
initUI() {
const list = document.getElementById('voiceOptionsList');
list.innerHTML = '';
for (const [code, name] of Object.entries(this.voiceMap)) {
const isSelected = code === this.selectedVoice ? 'selected' : '';
const checkIcon = isSelected ? `<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>` : `<div></div>`;
list.innerHTML += `<div class="dropdown-item ${isSelected}" onclick="TTSManager.setVoice('${code}')"><span>${name}</span>${checkIcon}</div>`;
}
const langList = document.getElementById('languageOptionsList');
langList.innerHTML = '';
for (const [name, code] of Object.entries(this.languageMap)) {
const isSelected = name === this.selectedLanguage ? 'selected' : '';
const checkIcon = isSelected ? `<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>` : `<div></div>`;
langList.innerHTML += `<div class="dropdown-item ${isSelected}" onclick="TTSManager.setLanguage('${name}')"><span>${name}</span>${checkIcon}</div>`;
}
},
toggleMenu() {
document.getElementById('voiceMenu').classList.toggle('active');
document.getElementById('attachMenu').classList.remove('active');
document.getElementById('thinkMenu').classList.remove('active');
},
setVoice(code) {
Object.values(this.cache).forEach(url => URL.revokeObjectURL(url));
this.cache = {};
this.selectedVoice = code;
this.initUI();
document.getElementById('voiceMenu').classList.remove('active');
},
setLanguage(name) {
Object.values(this.cache).forEach(url => URL.revokeObjectURL(url));
this.cache = {};
this.selectedLanguage = name;
this.initUI();
document.getElementById('voiceMenu').classList.remove('active');
},
cleanTextForTTS(text) {
let cleaned = text.replace(/\u0060{3}[\s\S]*?\u0060{3}/g, '')
.replace(/\u0060[^\u0060]*\u0060/g, '')
.replace(/<think>[\s\S]*?<\/think>/gi, '')
.replace(/https?:\/\/[^\s]+/g, '')
.replace(/[*_#\u0060~>]/g, '')
.replace(/<[^>]*>/g, '')
.replace(/\s+/g, ' ')
.trim();
cleaned = cleaned.replace(/[\u{1F600}-\u{1F64F}]/gu, '')
.replace(/[\u{1F300}-\u{1F5FF}]/gu, '')
.replace(/[\u{1F680}-\u{1F6FF}]/gu, '')
.replace(/[\u{2600}-\u{26FF}]/gu, '')
.replace(/[\u{2700}-\u{27BF}]/gu, '');
if (cleaned.length > 1500) {
cleaned = cleaned.substring(0, 1500) + '...';
}
return cleaned;
},
async autoPrepare(msgId, fullText, btnElement) {
if (this.preparingSet.has(msgId)) return;
const cleanText = this.cleanTextForTTS(fullText);
if (!cleanText) {
btnElement.style.display = 'none';
return;
}
this.preparingSet.add(msgId);
btnElement.style.display = 'flex';
btnElement.innerHTML = `<svg class="spin-icon" style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg> Preparing...`;
btnElement.style.pointerEvents = 'none';
try {
const response = await fetch('/api/tts', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
text: cleanText,
voice: this.selectedVoice,
language_name: this.selectedLanguage
})
});
if (!response.ok) throw new Error('TTS service unavailable');
const blob = await response.blob();
this.cache[msgId] = URL.createObjectURL(blob);
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> Listen`;
btnElement.style.pointerEvents = 'auto';
} catch(e) {
btnElement.innerHTML = `<span style="color:var(--brand-danger);">⚠️ ${e.message}</span>`;
setTimeout(() => { btnElement.style.display = 'none'; }, 4000);
} finally {
this.preparingSet.delete(msgId);
}
},
play(msgId, btnElement) {
const audioUrl = this.cache[msgId];
if (!audioUrl) {
this.autoPrepare(msgId, document.getElementById(`bot-content-${msgId}`).innerText, btnElement);
return;
}
if (this.isPlaying && this.currentMsgId === msgId) {
if (this.currentAudio) {
this.currentAudio.pause();
this.currentAudio.currentTime = 0;
}
this.isPlaying = false;
this.currentMsgId = null;
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> Listen`;
btnElement.style.background = '';
return;
}
if (this.currentAudio) {
this.currentAudio.pause();
this.currentAudio.currentTime = 0;
}
this.isPlaying = true;
this.currentMsgId = msgId;
this.currentBtnElement = btnElement;
const originalHTML = btnElement.innerHTML;
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg> Playing...`;
btnElement.style.background = 'rgba(211, 227, 253, 0.9)';
this.currentAudio = new Audio(audioUrl);
this.currentAudio.playbackRate = 1.15;
this.currentAudio.onended = () => {
this.isPlaying = false;
this.currentMsgId = null;
btnElement.innerHTML = originalHTML;
btnElement.style.background = '';
};
this.currentAudio.onerror = () => {
this.isPlaying = false;
this.currentMsgId = null;
btnElement.innerHTML = originalHTML;
btnElement.style.background = '';
};
this.currentAudio.play();
}
};
const Chat = {
renderUser(txt, attachUI = '') {
const c = document.getElementById('chatMessages');
const w = document.createElement('div');
w.className = 'message-wrapper';
w.innerHTML = `<div class="user-message">${attachUI}${txt ? UI.escape(txt) : ''}</div>`;
c.appendChild(w);
UI.autoScroll = true;
UI.scrollToBottom(true);
},
renderBot(msgId) {
const c = document.getElementById('chatMessages');
const w = document.createElement('div');
w.className = 'message-wrapper';
const listenIcon = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>`;
const copyIcon = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
w.innerHTML = `
<div class="bot-message">
<div class="bot-avatar"><img src="${Config.LOGO}"></div>
<div class="bot-content-wrapper">
<div class="bot-content" id="bot-content-${msgId}"></div>
<div class="msg-actions">
<button class="action-btn listen-btn" id="listen-btn-${msgId}" onclick="TTSManager.play('${msgId}', this)" style="display:none;">${listenIcon} Listen</button>
<button class="action-btn" onclick="Chat.copyMsg('${msgId}')">${copyIcon} Copy</button>
</div>
</div>
</div>`;
c.appendChild(w);
UI.scrollToBottom();
return {
contentDiv: document.getElementById(`bot-content-${msgId}`),
listenBtn: document.getElementById(`listen-btn-${msgId}`)
};
},
preprocessMath(text) {
let mathBlocks = {};
let mathCounter = 0;
function replacer(match) {
// Changed placeholder to completely avoid underscores or any special markdown characters
const id = `MATHBLOCKPLACEHOLDER${mathCounter}ENDPLACEHOLDER`;
mathBlocks[id] = match;
mathCounter++;
return id;
}
text = text.replace(/\$\$([\s\S]+?)\$\$/g, replacer);
text = text.replace(/\\\[([\s\S]+?)\\\]/g, replacer);
text = text.replace(/\$((?:\\.|[^$\\])+?)\$/g, replacer);
text = text.replace(/\\\(([\s\S]+?)\\\)/g, replacer);
return { text, mathBlocks };
},
postprocessMath(html, mathBlocks) {
for (const [id, mathStr] of Object.entries(mathBlocks)) {
// Using split.join to ensure global replace regardless of markdown alterations
html = html.split(id).join(mathStr);
}
return html;
},
parseAndRender(fullText, isSearching, isProcessing, container) {
let normalizedText = fullText
.replace(/<\|channel\|>thought\s*<\|channel\|>/gi, "<think>\n")
.replace(/<\|channel\|>answer\s*<\|channel\|>/gi, "\n</think>\n")
.replace(/<\|im_start\|>thought/gi, "<think>\n")
.replace(/<\|im_end\|>/gi, "\n</think>\n");
const thinkRegex = /<think>([\s\S]*?)(?:<\/think>|$)/i;
const thinkMatch = normalizedText.match(thinkRegex);
let finalHtml = '';
if (thinkMatch) {
finalHtml += `
<details class="qwen-think-box" ${isProcessing ? 'open' : ''}>
<summary>
<svg class="arrow" style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
Thinking Process
</summary>
<div class="qwen-think-content">${marked.parse(thinkMatch[1].trim())}</div>
</details>`;
}
let mainText = normalizedText.replace(thinkRegex, '').trim();
if (mainText) {
const { text: safeText, mathBlocks } = Chat.preprocessMath(mainText);
let parsedHtml = marked.parse(safeText);
parsedHtml = Chat.postprocessMath(parsedHtml, mathBlocks);
finalHtml += `<div class="tex2jax_process">${parsedHtml}</div>`;
}
if (isProcessing) {
finalHtml += `<span class="blinking-cursor"></span>`;
}
container.innerHTML = finalHtml;
runRendering(container, isProcessing);
},
copyMsg(id) {
const el = document.getElementById(`bot-content-${id}`);
const clone = el.cloneNode(true);
const thinkBox = clone.querySelector('.qwen-think-box');
if (thinkBox) {
thinkBox.remove();
}
navigator.clipboard.writeText(clone.innerText).then(() => {
const btn = el.parentElement.querySelector('.action-btn:not(.listen-btn)');
const original = btn.innerHTML;
btn.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!`;
setTimeout(() => btn.innerHTML = original, 1500);
});
},
stopGeneration() {
if (State.abortController) {
State.abortController.abort();
State.abortController = null;
}
State.isProcessing = false;
document.getElementById('btnSend').disabled = false;
document.getElementById('btnSend').style.display = 'flex';
document.getElementById('btnStop').classList.remove('active');
},
async handleSend() {
if(State.isProcessing) return;
if (!State.user && State.guestCount >= 10) {
Auth.openModal();
UI.showAuthMsg("Guest limit reached (10/10). Please login or register to continue.", true);
return;
}
const input = document.getElementById('mainInput');
const text = input.value.trim();
if(!text && !State.attachment) return;
if (!State.user) {
let count = parseInt(localStorage.getItem('codeved_guest') || '0', 10);
if (isNaN(count)) count = 0;
count++;
State.guestCount = count;
localStorage.setItem('codeved_guest', count.toString());
document.getElementById('uSub').innerText = `Queries: ${count}/10`;
}
State.isProcessing = true;
document.getElementById('btnSend').style.display = 'none';
document.getElementById('btnStop').classList.add('active');
State.lastUserMessage = text;
input.value = '';
input.style.height = 'auto';
UI.updateWelcomeScreen();
let payloadStr = text, attachUI = '', mediaArray = [];
if(State.attachment) {
if(State.attachment.type === 'text') {
payloadStr = `[File attached: ${State.attachment.name}]\n\n---DATA---\n${State.attachment.data}\n---END DATA---\n\nUser: ${text}`;
attachUI = `<div class="chat-attachment-container"><div class="chat-file-pill"><svg style="width:14px;height:14px;color:var(--text-secondary);" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path></svg> ${State.attachment.name}</div></div>`;
} else {
mediaArray.push(State.attachment);
attachUI = `<div class="chat-attachment-container"><img src="data:image/jpeg;base64,${State.attachment.data}" class="chat-img-preview"></div>`;
}
}
let systemInjectedPayload = payloadStr;
if(State.weatherContext) {
systemInjectedPayload = `${payloadStr}\n\n[SYSTEM REAL-TIME WEATHER: ${State.weatherContext}]`;
}
Chat.renderUser(text, attachUI);
State.history.push({ role: 'user', content: payloadStr });
FileSys.discard();
const msgId = Date.now().toString();
const botObj = Chat.renderBot(msgId);
Chat.parseAndRender("", false, true, botObj.contentDiv);
State.abortController = new AbortController();
let fullText = "";
try {
const res = await fetch(Config.API_ENDPOINT, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
message: systemInjectedPayload,
attachments: mediaArray,
is_search: State.searchEnabled,
location: State.location,
thinking_mode: State.thinkingMode,
thinking_effort: State.thinkingEffort,
history: State.history
}),
signal: State.abortController.signal
});
if(!res.ok) {
let errMsg = `Server error (${res.status})`;
if (res.status === 404) {
errMsg = 'Chat API endpoint not found (404). Please ensure the server is running.';
} else if (res.status === 500) {
errMsg = 'Internal server error (500). Please try again later.';
}
throw new Error(errMsg);
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while(true) {
const {done, value} = await reader.read();
if(done) break;
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('\n');
buffer = lines.pop();
for(let line of lines) {
if(line.startsWith('data: ')) {
const dataStr = line.substring(6).trim();
if(dataStr === '[DONE]') continue;
try {
const json = JSON.parse(dataStr);
if (json.error) {
botObj.contentDiv.innerHTML += `<br><span style="color:var(--brand-danger);">⚠️ ${UI.escape(json.error)}</span>`;
return;
}
if(json.choices && json.choices[0] && json.choices[0].delta && json.choices[0].delta.content) {
fullText += json.choices[0].delta.content;
}
} catch(e) {}
Chat.parseAndRender(fullText, false, true, botObj.contentDiv);
}
}
}
Chat.parseAndRender(fullText, false, false, botObj.contentDiv);
State.history.push({ role: 'assistant', content: fullText });
HistoryManager.saveCurrent();
} catch(e) {
if (e.name === 'AbortError') {
botObj.contentDiv.innerHTML += `<br><span style="color:var(--text-tertiary); font-style:italic;">⏹ Generation stopped.</span>`;
if (fullText.trim()) {
State.history.push({ role: 'assistant', content: fullText });
HistoryManager.saveCurrent();
}
} else {
botObj.contentDiv.innerHTML += `<br><span style="color:var(--brand-danger);">⚠️ ${UI.escape(e.message || 'Connection Offline.')}</span>`;
}
} finally {
State.isProcessing = false;
document.getElementById('btnSend').disabled = false;
document.getElementById('btnSend').style.display = 'flex';
document.getElementById('btnStop').classList.remove('active');
if (UI.autoScroll) UI.scrollToBottom();
if(botObj.listenBtn && fullText.trim().length > 0) {
TTSManager.autoPrepare(msgId, fullText, botObj.listenBtn);
}
}
}
};
// ग्लोबल स्कोप में एक्सपोज़ करें
window.UI = UI;
window.Auth = Auth;
window.HistoryManager = HistoryManager;
window.EnvironmentManager = EnvironmentManager;
window.SearchManager = SearchManager;
window.ThinkingManager = ThinkingManager;
window.FileSys = FileSys;
window.Speech = Speech;
window.TTSManager = TTSManager;
window.Chat = Chat;
// मेरमेड इनिशियलाइज़ करें
initMermaid();
// विंडो लोड होने पर इनिशियलाइज़ेशन
window.onload = () => {
Auth.init();
TTSManager.initUI();
UI.updateWelcomeScreen();
if(window.innerWidth > 900) {
document.getElementById('sidebar').classList.remove('collapsed');
}
};
})();
</script>
</body>
</html>