Spaces:
Running
Running
Vedika commited on
Update index.html
Browse files- index.html +835 -323
index.html
CHANGED
|
@@ -4,45 +4,75 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
<title>Arjun 2.O - Advanced AI</title>
|
|
|
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 9 |
-
<
|
|
|
|
| 10 |
<!-- PDF.js for PDF to Text Extraction -->
|
| 11 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
| 12 |
<!-- Mammoth.js for Word (.docx) to Text Extraction -->
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
|
| 14 |
-
|
| 15 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 16 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
|
|
|
| 17 |
<style>
|
| 18 |
-
/*
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
--
|
| 24 |
-
--
|
| 25 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
--shadow-lg: 0 16px 48px rgba(0,0,0,0.12);
|
| 27 |
-
--font-serif: 'Instrument Serif', serif;
|
|
|
|
| 28 |
--font-mono: 'Geist Mono', monospace;
|
| 29 |
-
--radius-lg: 24px;
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
-
html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--text-main);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;}
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
#toast-container { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); z-index: 10000; display: flex; flex-direction: column; gap: 10px; }
|
| 42 |
.toast { background: var(--text-main); color: #fff; padding: 12px 24px; border-radius: 50px; font-size: 13px; font-weight: 600; box-shadow: var(--shadow-md); animation: slideDownFade 0.4s ease; }
|
| 43 |
@keyframes slideDownFade { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
|
| 44 |
|
| 45 |
-
/*
|
|
|
|
|
|
|
| 46 |
.welcome-overlay { position: fixed; inset: 0; background: rgba(255,255,255,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 20px; transition: opacity 0.4s ease; }
|
| 47 |
.welcome-overlay.hidden { opacity: 0; pointer-events: none; }
|
| 48 |
.welcome-overlay img { width: 120px; height: 120px; border-radius: 28px; object-fit: cover; box-shadow: var(--shadow-lg); margin-bottom: 24px; border: 1px solid var(--border-light); }
|
|
@@ -51,24 +81,28 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 51 |
.start-btn { padding: 16px 40px; background: var(--text-main); color: #fff; border: none; border-radius: 16px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s; box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
|
| 52 |
.start-btn:hover { background: #333; transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0,0,0,0.2); }
|
| 53 |
|
| 54 |
-
/*
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
.
|
| 58 |
-
.
|
| 59 |
-
.
|
| 60 |
-
.
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
.logo-
|
| 68 |
-
.logo-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
.close-sidebar-btn { display: none; background: none; border: none; font-size: 20px; color: var(--text-main); cursor: pointer; }
|
| 70 |
|
| 71 |
-
/* Auth Block
|
| 72 |
.auth-block { padding: 20px; border-bottom: 1px solid var(--border-light); }
|
| 73 |
.user-profile { display: none; align-items: center; gap: 10px; background: #fff; padding: 10px; border-radius: 12px; border: 1px solid var(--border-light); box-shadow: var(--shadow-sm); }
|
| 74 |
.user-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--text-main); color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px; }
|
|
@@ -77,114 +111,133 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 77 |
.login-prompt-btn { width: 100%; padding: 12px; border-radius: 12px; background: var(--text-main); color: #fff; border: none; font-size: 13px; font-weight: 600; cursor: pointer; transition: 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; }
|
| 78 |
.login-prompt-btn:hover { background: #333; transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
| 79 |
|
| 80 |
-
|
| 81 |
-
.
|
| 82 |
-
.brand-box
|
| 83 |
-
.brand-box
|
| 84 |
-
.brand-box
|
| 85 |
-
|
| 86 |
-
.clear-btn{width:100%;padding:12px;border-radius:var(--radius-sm);background:#ffffff;border:1px solid var(--border-light);color:var(--text-main);font-size:13px;font-weight:600;cursor:pointer;transition:all 0.2s;}
|
| 87 |
-
.clear-btn:hover{background:#fff0f0;color:#ff3b30;border-color:#ff3b30;}
|
| 88 |
-
|
| 89 |
-
/*
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
.mobile-menu-btn { display: none; background: none; border: none; font-size: 24px; color: var(--text-main); cursor: pointer; margin-right: 16px; }
|
| 92 |
-
.model-badge{font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px;}
|
| 93 |
-
.status-dot{width:8px;height:8px;border-radius:50%;background:#34c759;box-shadow:0 0 0 2px rgba(52,199,89,0.2);}
|
| 94 |
-
|
| 95 |
-
.messages{flex:1;overflow-y:auto;padding:30px 40px;display:flex;flex-direction:column;gap:24px;scroll-behavior:smooth;position:relative; min-height:0;}
|
| 96 |
-
.messages::-webkit-scrollbar{width:6px;}
|
| 97 |
-
.messages::-webkit-scrollbar-thumb{background:rgba(0,0,0,0.1);border-radius:10px;}
|
| 98 |
-
|
| 99 |
-
/* Welcome Screen */
|
| 100 |
-
.welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:40px 20px;margin:auto;animation:fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;width:100%;max-width:650px;}
|
| 101 |
-
@keyframes fadeInUp{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:none;}}
|
| 102 |
-
.welcome-logo{width:80px;height:80px;border-radius:24px;object-fit:cover;box-shadow:var(--shadow-md);margin-bottom:24px;border:1px solid var(--border-light);}
|
| 103 |
-
.welcome-title{font-size:32px;font-weight:800;letter-spacing:-1px;margin-bottom:12px;}
|
| 104 |
-
.welcome-sub{font-size:15px;color:var(--text-muted);line-height:1.6;margin-bottom:20px;max-width: 500px;}
|
| 105 |
.guest-badge { background: #fff0f0; color: #ff3b30; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 700; margin-bottom: 24px; border: 1px solid #ffcccb; display: none; }
|
| 106 |
|
| 107 |
-
.ex-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;width:100%;}
|
| 108 |
-
.ex-card{background:var(--glass-bg);backdrop-filter:blur(10px);border:1px solid var(--border-light);border-radius:var(--radius-md);padding:16px;cursor:pointer;text-align:left;transition:all 0.2s;box-shadow:var(--shadow-sm);}
|
| 109 |
-
.ex-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-md);border-color:var(--accent);}
|
| 110 |
-
.ex-title{font-size:13px;font-weight:600;color:var(--text-main);margin-bottom:4px;display:flex;align-items:center;gap:6px;}
|
| 111 |
-
.ex-desc{font-size:12px;color:var(--text-muted);line-height:1.4;}
|
| 112 |
-
|
| 113 |
-
/*
|
| 114 |
-
.msg{display:flex;gap:16px;animation:msgIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;max-width:85%;}
|
| 115 |
-
@keyframes msgIn{from{opacity:0;transform:translateY(15px) scale(0.96);}to{opacity:1;transform:none;}}
|
| 116 |
-
.msg.user{flex-direction:row-reverse;align-self:flex-end;}
|
| 117 |
-
.msg.bot{align-self:flex-start;}
|
| 118 |
-
.avatar{width:36px;height:36px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;overflow:hidden;}
|
| 119 |
-
.msg.user .avatar{background:#f0f0f0;color:var(--text-main);border:1px solid var(--border-light);}
|
| 120 |
-
.msg.bot .avatar{border:1px solid var(--border-light);background:#fff;padding:2px;}
|
| 121 |
-
.msg.bot .avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;}
|
| 122 |
-
.body{display:flex;flex-direction:column;gap:6px;max-width:100%; width: 100%;}
|
| 123 |
-
.msg.user .body{align-items:flex-end;}
|
| 124 |
-
|
| 125 |
-
.bubble{padding:16px 20px;font-size:15px;line-height:1.6;color:var(--text-main);position:relative;word-break:break-word;overflow-wrap:anywhere;}
|
| 126 |
-
.msg.user .bubble{background:var(--text-main);color:#ffffff;border-radius:20px 20px 4px 20px;box-shadow:var(--shadow-sm);}
|
| 127 |
-
.msg.bot .bubble{background:#ffffff;border:1px solid var(--border-light);border-radius:4px 20px 20px 20px;box-shadow:var(--shadow-sm);width:100%;}
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
.msg.
|
| 131 |
-
.
|
| 132 |
-
.action-btn
|
| 133 |
-
|
| 134 |
-
.
|
| 135 |
-
|
| 136 |
-
/*
|
| 137 |
-
.
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
.
|
| 141 |
-
.bubble
|
| 142 |
-
.bubble
|
| 143 |
-
.bubble
|
| 144 |
-
|
| 145 |
-
.
|
| 146 |
-
.
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
/* Typing & Cursor */
|
| 154 |
-
.blinking-cursor {display: inline-block; width: 8px; height: 16px; background: var(--accent); margin-left: 4px; animation: blink 1s step-end infinite; vertical-align: middle;}
|
| 155 |
@keyframes blink { 50% { opacity: 0; } }
|
| 156 |
-
.typing{display:flex;align-items:center;gap:6px;padding:4px;}
|
| 157 |
-
.typing span{width:6px;height:6px;border-radius:50%;background:var(--text-muted);opacity:0.4;animation:bounce 1.4s infinite ease-in-out both;}
|
| 158 |
-
.typing span:nth-child(1){animation-delay:-0.32s;
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
.scroll-bottom-btn
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
.
|
| 171 |
-
.
|
| 172 |
-
.
|
| 173 |
-
|
| 174 |
-
.
|
| 175 |
-
.
|
| 176 |
-
.inline-
|
| 177 |
-
|
| 178 |
-
.
|
| 179 |
-
.
|
| 180 |
-
.
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
.attach-wrapper { position: relative; }
|
| 184 |
-
.attach-btn{width:38px;height:38px;border-radius:12px;background:transparent;border:1px solid transparent;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-muted);transition:all 0.2s;position:relative;overflow:hidden;padding-bottom:4px;}
|
| 185 |
-
.attach-btn:hover{background:rgba(0,0,0,0.04);color:var(--text-main);}
|
| 186 |
-
.attach-btn svg{width:20px;height:20px;}
|
| 187 |
-
.attach-menu { position: absolute; bottom: 50px; left: 0; background: rgba(255,255,255,0.
|
| 188 |
.attach-menu.show { display: flex; transform: translateY(0); opacity: 1; }
|
| 189 |
.attach-menu-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; cursor: pointer; font-size: 14px; font-weight: 500; color: var(--text-main); transition: background 0.2s; }
|
| 190 |
.attach-menu-item:hover { background: rgba(0,0,0,0.05); }
|
|
@@ -196,27 +249,33 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 196 |
.voice-btn.recording { color: #ff3b30; animation: pulseRecord 1.5s infinite; }
|
| 197 |
@keyframes pulseRecord { 0% { background: rgba(255,59,48,0.1); } 50% { background: rgba(255,59,48,0.3); } 100% { background: rgba(255,59,48,0.1); } }
|
| 198 |
|
| 199 |
-
.send-btn{width:42px;height:42px;border-radius:14px;border:none;background:linear-gradient(135deg, #FF9933 0%, #ffffff 50%, #138808 100%);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.3s;box-shadow:0 4px 12px rgba(0,0,0,0.1);}
|
| 200 |
-
.send-btn svg{width:18px;height:18px;fill:#1d1d1f;transition:transform 0.2s;}
|
| 201 |
-
.send-btn:hover{box-shadow:0 6px 16px rgba(255,153,51,0.3);transform:translate(2px, -2px);}
|
| 202 |
-
.send-btn:disabled{opacity:0.4;cursor:not-allowed;filter:grayscale(100%);transform:none;}
|
| 203 |
-
.input-hint{text-align:center;font-size:11px;color:var(--text-muted);margin-top:12px;font-weight:500;}
|
| 204 |
-
|
| 205 |
-
/*
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
.ws-
|
| 209 |
-
.ws-
|
| 210 |
-
.ws-
|
| 211 |
-
.ws-
|
| 212 |
-
.ws-
|
| 213 |
-
.ws-
|
| 214 |
-
.ws-
|
| 215 |
-
|
| 216 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
.modal-overlay { position: fixed; inset: 0; background: rgba(255,255,255,0.85); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 5000; display: none; justify-content: center; align-items: center; animation: fadeIn 0.3s ease; }
|
| 218 |
.modal-card { background: #fff; padding: 32px 36px; border-radius: 24px; width: 90%; max-width: 420px; border: 1px solid var(--border-light); box-shadow: var(--shadow-lg); position: relative; }
|
| 219 |
-
.close-modal { position: absolute; top: 20px; right: 20px; background: none; border: none; font-size: 24px; color: var(--text-muted); cursor: pointer; transition: 0.2s;}
|
| 220 |
.close-modal:hover { color: var(--text-main); transform: rotate(90deg); }
|
| 221 |
.auth-header { text-align: center; margin-bottom: 24px; }
|
| 222 |
.auth-header h2 { font-family: var(--font-sans); font-size: 24px; font-weight: 800; color: var(--text-main); }
|
|
@@ -233,23 +292,28 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 233 |
.secondary-btn { width: 100%; padding: 12px; background: transparent; color: var(--text-muted); border: none; font-size: 14px; font-weight: 600; cursor: pointer; margin-top: 4px; }
|
| 234 |
.secondary-btn:hover { color: var(--text-main); }
|
| 235 |
|
|
|
|
|
|
|
|
|
|
| 236 |
@media(max-width:850px){
|
| 237 |
-
.sidebar{position:absolute;left:-280px;z-index:100;height:100%;transition:left 0.3s ease;}
|
| 238 |
-
.sidebar.show{left:0;box-shadow:var(--shadow-lg);}
|
| 239 |
.mobile-menu-btn { display: block; }
|
| 240 |
.close-sidebar-btn { display: block; }
|
| 241 |
-
.workspace.open{position:absolute;top:0;left:0;width:100%;height:100%;z-index:50;}
|
| 242 |
-
.messages{padding:20px;}
|
| 243 |
-
.input-container{padding:10px 15px 15px;}
|
| 244 |
-
.msg{max-width:95%;}
|
| 245 |
-
.scroll-bottom-btn{bottom:110px;}
|
| 246 |
}
|
| 247 |
</style>
|
| 248 |
</head>
|
| 249 |
<body>
|
| 250 |
|
|
|
|
| 251 |
<div id="toast-container"></div>
|
| 252 |
|
|
|
|
| 253 |
<div class="welcome-overlay" id="welcomeOverlay">
|
| 254 |
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo">
|
| 255 |
<h1>Welcome to Arjun 2.O</h1>
|
|
@@ -257,9 +321,11 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 257 |
<button class="start-btn" onclick="startArjunApp()">Get Started</button>
|
| 258 |
</div>
|
| 259 |
|
|
|
|
| 260 |
<div class="bg"><div class="orb orb1"></div><div class="orb orb2"></div></div>
|
| 261 |
|
| 262 |
<div class="shell">
|
|
|
|
| 263 |
<aside class="sidebar" id="sidebar">
|
| 264 |
<div class="logo-area">
|
| 265 |
<div class="logo-row">
|
|
@@ -291,6 +357,7 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 291 |
</div>
|
| 292 |
</aside>
|
| 293 |
|
|
|
|
| 294 |
<div class="main-wrapper">
|
| 295 |
<main class="chat-main" id="chatMain">
|
| 296 |
<header class="chat-hdr">
|
|
@@ -303,6 +370,7 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 303 |
</header>
|
| 304 |
|
| 305 |
<div class="messages" id="msgs" onscroll="checkScroll()">
|
|
|
|
| 306 |
<div class="welcome" id="welcome">
|
| 307 |
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo" class="welcome-logo">
|
| 308 |
<div class="welcome-title">Welcome, User!</div>
|
|
@@ -325,6 +393,7 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 325 |
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
|
| 326 |
</button>
|
| 327 |
|
|
|
|
| 328 |
<div class="input-container">
|
| 329 |
<div class="input-wrap">
|
| 330 |
<div class="img-prev-inline" id="imgPrev">
|
|
@@ -355,9 +424,11 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 355 |
<input type="file" id="inp-image" class="hidden-file-input" accept="image/*" onchange="handleSpecificFile(this, 'image')">
|
| 356 |
<input type="file" id="inp-video" class="hidden-file-input" accept="video/*" onchange="handleSpecificFile(this, 'video')">
|
| 357 |
<input type="file" id="inp-audio" class="hidden-file-input" accept="audio/*" onchange="handleSpecificFile(this, 'audio')">
|
| 358 |
-
<
|
|
|
|
| 359 |
</div>
|
| 360 |
|
|
|
|
| 361 |
<button class="attach-btn voice-btn" id="voiceBtn" onclick="toggleVoice()" title="Voice Input">
|
| 362 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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" x2="12" y1="19" y2="22"></line></svg>
|
| 363 |
</button>
|
|
@@ -373,21 +444,24 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 373 |
</div>
|
| 374 |
</main>
|
| 375 |
|
|
|
|
| 376 |
<aside class="workspace" id="workspace">
|
| 377 |
<div class="ws-hdr">
|
| 378 |
<div class="ws-title"><span>๐ป</span><span id="wsTitleText">Generated Code</span></div>
|
| 379 |
<div class="ws-actions">
|
| 380 |
-
<button class="ws-icon-btn" onclick="copyWorkspaceCode()" id="wsCopyBtn">๐ Copy</button>
|
| 381 |
<button class="ws-icon-btn ws-close" onclick="closeWorkspace()" title="Close">โ</button>
|
| 382 |
</div>
|
| 383 |
</div>
|
| 384 |
<div class="ws-content">
|
|
|
|
| 385 |
<pre><code id="wsCodeBlock" class="hljs"></code></pre>
|
| 386 |
</div>
|
| 387 |
</aside>
|
| 388 |
</div>
|
| 389 |
</div>
|
| 390 |
|
|
|
|
| 391 |
<div class="modal-overlay" id="authModal">
|
| 392 |
<div class="modal-card">
|
| 393 |
<button class="close-modal" onclick="closeAuthModal()">โ</button>
|
|
@@ -396,6 +470,7 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 396 |
<div class="auth-tab active" id="tab-login" onclick="switchAuthTab('login')">Login</div>
|
| 397 |
<div class="auth-tab" id="tab-register" onclick="switchAuthTab('register')">Register</div>
|
| 398 |
</div>
|
|
|
|
| 399 |
<div id="flow-login">
|
| 400 |
<div class="auth-step active" id="log-step-1">
|
| 401 |
<div class="input-group"><input type="email" id="logEmail" placeholder="Registered Email"></div>
|
|
@@ -407,6 +482,7 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 407 |
<button class="secondary-btn" onclick="switchAuthStep('log-step-2', 'log-step-1')">Back</button>
|
| 408 |
</div>
|
| 409 |
</div>
|
|
|
|
| 410 |
<div id="flow-register" style="display:none;">
|
| 411 |
<div class="auth-step active" id="reg-step-1">
|
| 412 |
<div class="input-group"><input type="text" id="regName" placeholder="Full Name"></div>
|
|
@@ -423,7 +499,10 @@ html,body{height:100dvh;overflow:hidden;background:var(--bg-color);color:var(--t
|
|
| 423 |
</div>
|
| 424 |
|
| 425 |
<script>
|
| 426 |
-
/
|
|
|
|
|
|
|
|
|
|
| 427 |
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
| 428 |
|
| 429 |
const GAS_URL = "https://script.google.com/macros/s/AKfycbzTsXGoVh97JjtFAAG825GF4WUZqf7FDB3Zbxyf0nrLXt_1HjJSSqA829UcdoamRPvg0A/exec";
|
|
@@ -433,14 +512,44 @@ let currentUser = localStorage.getItem('arjun_user');
|
|
| 433 |
let guestCount = parseInt(localStorage.getItem('arjun_guest') || '0');
|
| 434 |
window.isStreamingGlobalFlag = false;
|
| 435 |
|
| 436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
|
|
|
|
|
|
|
|
|
|
| 438 |
document.addEventListener('DOMContentLoaded', () => {
|
| 439 |
-
|
| 440 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
updateAuthUI();
|
| 442 |
-
|
| 443 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
});
|
| 445 |
|
| 446 |
function startArjunApp() {
|
|
@@ -451,68 +560,304 @@ function startArjunApp() {
|
|
| 451 |
}
|
| 452 |
|
| 453 |
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('show'); }
|
|
|
|
| 454 |
function showToast(msg) {
|
| 455 |
const container = document.getElementById('toast-container');
|
| 456 |
const t = document.createElement('div'); t.className = 'toast'; t.innerText = msg;
|
| 457 |
-
container.appendChild(t);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
}
|
| 459 |
|
| 460 |
-
/
|
|
|
|
|
|
|
| 461 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 462 |
-
let recognition = null;
|
|
|
|
| 463 |
|
| 464 |
if (SpeechRecognition) {
|
| 465 |
recognition = new SpeechRecognition();
|
| 466 |
recognition.continuous = false;
|
| 467 |
recognition.interimResults = false;
|
| 468 |
|
| 469 |
-
//
|
| 470 |
recognition.lang = 'hi-IN';
|
| 471 |
|
| 472 |
-
recognition.onstart = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
recognition.onresult = (event) => {
|
| 474 |
let transcript = '';
|
| 475 |
-
for (let i = event.resultIndex; i < event.results.length; ++i)
|
|
|
|
|
|
|
| 476 |
transcript = transcript.trim();
|
| 477 |
if(transcript.length > 0) {
|
|
|
|
| 478 |
transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
|
| 479 |
-
|
|
|
|
|
|
|
| 480 |
}
|
| 481 |
const inp = document.getElementById('chatInput');
|
| 482 |
inp.value = (inp.value + ' ' + transcript).trim();
|
| 483 |
autoGrow(inp);
|
| 484 |
};
|
| 485 |
-
|
| 486 |
-
recognition.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
}
|
|
|
|
| 488 |
function toggleVoice() {
|
| 489 |
if(!recognition) return showToast("Voice input not supported in this browser.");
|
| 490 |
if(isRecording) recognition.stop(); else recognition.start();
|
| 491 |
}
|
| 492 |
|
| 493 |
-
/
|
| 494 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
function openAuthModal() { document.getElementById('authModal').style.display = 'flex'; }
|
| 496 |
function closeAuthModal() { document.getElementById('authModal').style.display = 'none'; }
|
| 497 |
-
function switchAuthTab(tab) {
|
| 498 |
-
|
| 499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
function logout() { localStorage.removeItem('arjun_user'); location.reload(); }
|
| 501 |
-
async function clearMemory() {
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
|
| 505 |
-
/
|
|
|
|
|
|
|
| 506 |
function toggleAttachMenu() {
|
| 507 |
const menu = document.getElementById('attachMenu');
|
| 508 |
menu.classList.toggle('show');
|
| 509 |
}
|
| 510 |
-
|
|
|
|
| 511 |
document.addEventListener('click', function(event) {
|
| 512 |
const wrapper = document.querySelector('.attach-wrapper');
|
| 513 |
-
if (!wrapper.contains(event.target)) {
|
|
|
|
|
|
|
| 514 |
});
|
| 515 |
|
|
|
|
| 516 |
function triggerInput(type) {
|
| 517 |
document.getElementById('attachMenu').classList.remove('show');
|
| 518 |
document.getElementById('inp-' + type).click();
|
|
@@ -523,11 +868,18 @@ async function handleSpecificFile(input, expectedType) {
|
|
| 523 |
const fileName = file.name.toLowerCase();
|
| 524 |
const fileMime = file.type;
|
| 525 |
|
| 526 |
-
// 1. STRICT VALIDATION
|
| 527 |
-
if (expectedType === 'image' && !fileMime.startsWith('image/')) {
|
| 528 |
-
|
| 529 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
|
|
|
|
| 531 |
document.getElementById('imgThumb').style.display = 'none';
|
| 532 |
document.getElementById('fileIcon').style.display = 'none';
|
| 533 |
document.getElementById('imgName').textContent = file.name;
|
|
@@ -537,7 +889,7 @@ async function handleSpecificFile(input, expectedType) {
|
|
| 537 |
|
| 538 |
const reader = new FileReader();
|
| 539 |
|
| 540 |
-
// 2.
|
| 541 |
if (expectedType === 'image') {
|
| 542 |
reader.onload = e => {
|
| 543 |
S.pending = { type: 'image', data: e.target.result.split(',')[1], name: file.name };
|
|
@@ -546,6 +898,7 @@ async function handleSpecificFile(input, expectedType) {
|
|
| 546 |
};
|
| 547 |
reader.readAsDataURL(file);
|
| 548 |
}
|
|
|
|
| 549 |
else if (expectedType === 'video' || expectedType === 'audio') {
|
| 550 |
reader.onload = e => {
|
| 551 |
S.pending = { type: expectedType, data: e.target.result.split(',')[1], name: file.name };
|
|
@@ -554,12 +907,12 @@ async function handleSpecificFile(input, expectedType) {
|
|
| 554 |
};
|
| 555 |
reader.readAsDataURL(file);
|
| 556 |
}
|
| 557 |
-
//
|
| 558 |
else if (expectedType === 'document') {
|
| 559 |
document.getElementById('fileIcon').textContent = '๐';
|
| 560 |
document.getElementById('fileIcon').style.display = 'flex';
|
| 561 |
|
| 562 |
-
// A.
|
| 563 |
if (fileName.endsWith('.pdf')) {
|
| 564 |
reader.onload = async function() {
|
| 565 |
try {
|
|
@@ -573,55 +926,121 @@ async function handleSpecificFile(input, expectedType) {
|
|
| 573 |
}
|
| 574 |
S.pending = { type: 'text', data: fullText.trim() || "No text found in PDF.", name: file.name };
|
| 575 |
showToast("PDF converted to text successfully!");
|
| 576 |
-
} catch (e) {
|
|
|
|
|
|
|
| 577 |
};
|
| 578 |
reader.readAsArrayBuffer(file);
|
| 579 |
}
|
| 580 |
-
// B.
|
| 581 |
-
else if (fileName.endsWith('.docx')) {
|
| 582 |
reader.onload = async function() {
|
| 583 |
try {
|
| 584 |
const arrayBuffer = this.result;
|
| 585 |
const result = await mammoth.extractRawText({arrayBuffer: arrayBuffer});
|
| 586 |
S.pending = { type: 'text', data: result.value.trim() || "No text found in DOCX.", name: file.name };
|
| 587 |
showToast("Word document converted to text!");
|
| 588 |
-
} catch(e) {
|
|
|
|
|
|
|
| 589 |
};
|
| 590 |
reader.readAsArrayBuffer(file);
|
| 591 |
}
|
| 592 |
-
// C.
|
| 593 |
else {
|
| 594 |
reader.onload = e => {
|
| 595 |
S.pending = { type: 'text', data: e.target.result, name: file.name };
|
| 596 |
-
showToast("Code/Text file attached!");
|
| 597 |
};
|
| 598 |
reader.readAsText(file);
|
| 599 |
}
|
| 600 |
}
|
| 601 |
-
|
|
|
|
|
|
|
| 602 |
}
|
| 603 |
|
| 604 |
function removeImg(){ S.pending=null; document.getElementById('imgPrev').classList.remove('show'); }
|
| 605 |
|
| 606 |
-
/
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
function
|
| 610 |
-
function
|
| 611 |
-
|
| 612 |
-
function
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
function closeWorkspace() { document.getElementById('workspace').classList.remove('open'); }
|
|
|
|
|
|
|
| 618 |
const renderer = new marked.Renderer();
|
| 619 |
renderer.code = function(token) {
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
};
|
| 626 |
marked.use({ renderer });
|
| 627 |
|
|
@@ -632,117 +1051,210 @@ function parseThinking(raw) {
|
|
| 632 |
return { mainText: text.trim(), thinkText: thinkContent.trim() };
|
| 633 |
}
|
| 634 |
|
| 635 |
-
/
|
|
|
|
|
|
|
| 636 |
async function sendMsg(){
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
|
|
|
| 675 |
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
|
|
|
|
|
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
if (
|
| 717 |
-
|
| 718 |
-
const
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
}
|
| 731 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
}
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
}catch(err){ window.isStreamingGlobalFlag = false; renderBotText(botDiv,`**Error:** Server communication failed.`, false); }
|
| 738 |
-
S.busy=false; document.getElementById('sendBtn').disabled=false; autoScroll();
|
| 739 |
}
|
| 740 |
|
| 741 |
-
function sendEx(t){document.getElementById('chatInput').value=t;sendMsg();}
|
| 742 |
-
|
| 743 |
-
function
|
| 744 |
-
|
| 745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
</script>
|
| 747 |
</body>
|
| 748 |
</html>
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
<title>Arjun 2.O - Advanced AI</title>
|
| 7 |
+
<!-- Google Fonts -->
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 10 |
+
<!-- Highlight.js for Syntax Highlighting -->
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
| 12 |
<!-- PDF.js for PDF to Text Extraction -->
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
| 14 |
<!-- Mammoth.js for Word (.docx) to Text Extraction -->
|
| 15 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
|
| 16 |
+
<!-- Highlight.js and Marked.js -->
|
| 17 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 18 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
| 19 |
+
|
| 20 |
<style>
|
| 21 |
+
/* ==========================================================================
|
| 22 |
+
BASE RESET & VARIABLES (เคเคงเคพเคฐเคญเฅเคค เคธเฅเคเคฟเคเคเฅเคธ)
|
| 23 |
+
========================================================================== */
|
| 24 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 25 |
+
:root {
|
| 26 |
+
--bg-color: #ffffff;
|
| 27 |
+
--bg-secondary: #f9f9fb;
|
| 28 |
+
--text-main: #1d1d1f;
|
| 29 |
+
--text-muted: #86868b;
|
| 30 |
+
--accent: #FF9933;
|
| 31 |
+
--accent-green: #138808;
|
| 32 |
+
--accent-hover: #e68a2e;
|
| 33 |
+
--border-light: rgba(0, 0, 0, 0.08);
|
| 34 |
+
--border-focus: rgba(255, 153, 51, 0.3);
|
| 35 |
+
--glass-bg: rgba(255, 255, 255, 0.85);
|
| 36 |
+
--glass-border: rgba(255, 255, 255, 0.4);
|
| 37 |
+
--shadow-sm: 0 2px 8px rgba(0,0,0,0.04);
|
| 38 |
+
--shadow-md: 0 8px 32px rgba(0,0,0,0.08);
|
| 39 |
--shadow-lg: 0 16px 48px rgba(0,0,0,0.12);
|
| 40 |
+
--font-serif: 'Instrument Serif', serif;
|
| 41 |
+
--font-sans: 'Inter', -apple-system, sans-serif;
|
| 42 |
--font-mono: 'Geist Mono', monospace;
|
| 43 |
+
--radius-lg: 24px;
|
| 44 |
+
--radius-md: 16px;
|
| 45 |
+
--radius-sm: 8px;
|
| 46 |
}
|
|
|
|
| 47 |
|
| 48 |
+
html, body {
|
| 49 |
+
height: 100dvh;
|
| 50 |
+
overflow: hidden;
|
| 51 |
+
background: var(--bg-color);
|
| 52 |
+
color: var(--text-main);
|
| 53 |
+
font-family: var(--font-sans);
|
| 54 |
+
-webkit-font-smoothing: antialiased;
|
| 55 |
+
}
|
| 56 |
|
| 57 |
+
/* ==========================================================================
|
| 58 |
+
BACKGROUND ANIMATIONS (เคฌเฅเคเคเฅเคฐเคพเคเคเคก เคเฅเฅเคเฅเค)
|
| 59 |
+
========================================================================== */
|
| 60 |
+
.bg { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; background: linear-gradient(135deg, rgba(255,153,51,0.05) 0%, rgba(255,255,255,1) 50%, rgba(19,136,8,0.05) 100%); }
|
| 61 |
+
.orb { position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.5; animation: drift 25s ease-in-out infinite alternate; }
|
| 62 |
+
.orb1 { width: 600px; height: 600px; background: radial-gradient(circle, rgba(255,153,51,0.2), transparent 70%); top: -150px; left: -100px; }
|
| 63 |
+
.orb2 { width: 500px; height: 500px; background: radial-gradient(circle, rgba(19,136,8,0.18), transparent 70%); bottom: -100px; right: -100px; animation-delay: -10s; }
|
| 64 |
+
@keyframes drift { 0% { transform: translate(0,0) scale(1); } 100% { transform: translate(60px,40px) scale(1.05); } }
|
| 65 |
+
|
| 66 |
+
/* ==========================================================================
|
| 67 |
+
TOAST NOTIFICATION SYSTEM (เคจเฅเคเคฟเคซเคฟเคเฅเคถเคจ)
|
| 68 |
+
========================================================================== */
|
| 69 |
#toast-container { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); z-index: 10000; display: flex; flex-direction: column; gap: 10px; }
|
| 70 |
.toast { background: var(--text-main); color: #fff; padding: 12px 24px; border-radius: 50px; font-size: 13px; font-weight: 600; box-shadow: var(--shadow-md); animation: slideDownFade 0.4s ease; }
|
| 71 |
@keyframes slideDownFade { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
|
| 72 |
|
| 73 |
+
/* ==========================================================================
|
| 74 |
+
WELCOME OVERLAY (เคชเฅเคฐเคพเคฐเคเคญเคฟเค เคธเฅเคเฅเคฐเฅเคจ)
|
| 75 |
+
========================================================================== */
|
| 76 |
.welcome-overlay { position: fixed; inset: 0; background: rgba(255,255,255,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 20px; transition: opacity 0.4s ease; }
|
| 77 |
.welcome-overlay.hidden { opacity: 0; pointer-events: none; }
|
| 78 |
.welcome-overlay img { width: 120px; height: 120px; border-radius: 28px; object-fit: cover; box-shadow: var(--shadow-lg); margin-bottom: 24px; border: 1px solid var(--border-light); }
|
|
|
|
| 81 |
.start-btn { padding: 16px 40px; background: var(--text-main); color: #fff; border: none; border-radius: 16px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s; box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
|
| 82 |
.start-btn:hover { background: #333; transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0,0,0,0.2); }
|
| 83 |
|
| 84 |
+
/* ==========================================================================
|
| 85 |
+
LAYOUT STRUCTURING (เคฎเฅเคเฅเคฏ เคขเคพเคเคเคพ)
|
| 86 |
+
========================================================================== */
|
| 87 |
+
.shell { position: relative; z-index: 1; display: flex; height: 100dvh; width: 100vw; overflow: hidden; }
|
| 88 |
+
.sidebar { width: 280px; display: flex; flex-direction: column; background: var(--bg-secondary); border-right: 1px solid var(--border-light); flex-shrink: 0; z-index: 100; transition: left 0.3s ease; }
|
| 89 |
+
.main-wrapper { flex: 1; display: flex; position: relative; overflow: hidden; }
|
| 90 |
+
.chat-main { flex: 1; display: flex; flex-direction: column; background: transparent; position: relative; min-width: 300px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); height: 100%; }
|
| 91 |
+
.workspace { width: 0; background: #fafafa; border-left: 1px solid var(--border-light); display: flex; flex-direction: column; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; box-shadow: -10px 0 30px rgba(0,0,0,0.02); z-index: 25; }
|
| 92 |
+
.workspace.open { width: 50%; }
|
| 93 |
+
|
| 94 |
+
/* ==========================================================================
|
| 95 |
+
SIDEBAR COMPONENTS (เคธเคพเคเคกเคฌเคพเคฐ เคคเคคเฅเคต)
|
| 96 |
+
========================================================================== */
|
| 97 |
+
.logo-area { padding: 24px 20px; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; }
|
| 98 |
+
.logo-row { display: flex; align-items: center; gap: 12px; }
|
| 99 |
+
.logo-img { width: 44px; height: 44px; border-radius: 12px; object-fit: cover; box-shadow: var(--shadow-sm); border: 1px solid var(--border-light); background: #fff; }
|
| 100 |
+
.logo-text { line-height: 1.2; }
|
| 101 |
+
.logo-name { font-family: var(--font-sans); font-weight: 800; font-size: 16px; color: var(--text-main); letter-spacing: -0.5px; }
|
| 102 |
+
.logo-sub { font-size: 10px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); }
|
| 103 |
.close-sidebar-btn { display: none; background: none; border: none; font-size: 20px; color: var(--text-main); cursor: pointer; }
|
| 104 |
|
| 105 |
+
/* Auth Block */
|
| 106 |
.auth-block { padding: 20px; border-bottom: 1px solid var(--border-light); }
|
| 107 |
.user-profile { display: none; align-items: center; gap: 10px; background: #fff; padding: 10px; border-radius: 12px; border: 1px solid var(--border-light); box-shadow: var(--shadow-sm); }
|
| 108 |
.user-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--text-main); color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px; }
|
|
|
|
| 111 |
.login-prompt-btn { width: 100%; padding: 12px; border-radius: 12px; background: var(--text-main); color: #fff; border: none; font-size: 13px; font-weight: 600; cursor: pointer; transition: 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; }
|
| 112 |
.login-prompt-btn:hover { background: #333; transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
| 113 |
|
| 114 |
+
/* Settings / Clear Button */
|
| 115 |
+
.settings { padding: 20px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; }
|
| 116 |
+
.brand-box { background: #ffffff; border: 1px solid var(--border-light); border-radius: var(--radius-md); padding: 24px 16px; text-align: center; box-shadow: var(--shadow-sm); margin-top: auto; }
|
| 117 |
+
.brand-box img { width: 70px; height: 70px; border-radius: 16px; object-fit: cover; margin-bottom: 12px; box-shadow: var(--shadow-sm); border: 1px solid var(--border-light); }
|
| 118 |
+
.brand-box h3 { font-family: var(--font-sans); font-size: 15px; font-weight: 700; color: var(--text-main); margin-bottom: 6px; }
|
| 119 |
+
.brand-box p { font-size: 10px; font-weight: 600; color: var(--accent-green); line-height: 1.4; letter-spacing: 0.5px; }
|
| 120 |
+
.clear-btn { width: 100%; padding: 12px; border-radius: var(--radius-sm); background: #ffffff; border: 1px solid var(--border-light); color: var(--text-main); font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
|
| 121 |
+
.clear-btn:hover { background: #fff0f0; color: #ff3b30; border-color: #ff3b30; }
|
| 122 |
+
|
| 123 |
+
/* ==========================================================================
|
| 124 |
+
MAIN CHAT AREA (เคฎเฅเคเฅเคฏ เคเฅเค เคเฅเคทเฅเคคเฅเคฐ)
|
| 125 |
+
========================================================================== */
|
| 126 |
+
.chat-hdr { padding: 14px 24px; border-bottom: 1px solid var(--border-light); background: var(--glass-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); display: flex; align-items: center; z-index: 10; flex-shrink: 0; }
|
| 127 |
.mobile-menu-btn { display: none; background: none; border: none; font-size: 24px; color: var(--text-main); cursor: pointer; margin-right: 16px; }
|
| 128 |
+
.model-badge { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
| 129 |
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #34c759; box-shadow: 0 0 0 2px rgba(52,199,89,0.2); }
|
| 130 |
+
|
| 131 |
+
.messages { flex: 1; overflow-y: auto; padding: 30px 40px; display: flex; flex-direction: column; gap: 24px; scroll-behavior: smooth; position: relative; min-height: 0; }
|
| 132 |
+
.messages::-webkit-scrollbar { width: 6px; }
|
| 133 |
+
.messages::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 10px; }
|
| 134 |
+
|
| 135 |
+
/* Inner Welcome Screen */
|
| 136 |
+
.welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px 20px; margin: auto; animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; width: 100%; max-width: 650px; }
|
| 137 |
+
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
|
| 138 |
+
.welcome-logo { width: 80px; height: 80px; border-radius: 24px; object-fit: cover; box-shadow: var(--shadow-md); margin-bottom: 24px; border: 1px solid var(--border-light); }
|
| 139 |
+
.welcome-title { font-size: 32px; font-weight: 800; letter-spacing: -1px; margin-bottom: 12px; }
|
| 140 |
+
.welcome-sub { font-size: 15px; color: var(--text-muted); line-height: 1.6; margin-bottom: 20px; max-width: 500px; }
|
| 141 |
.guest-badge { background: #fff0f0; color: #ff3b30; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 700; margin-bottom: 24px; border: 1px solid #ffcccb; display: none; }
|
| 142 |
|
| 143 |
+
.ex-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; width: 100%; }
|
| 144 |
+
.ex-card { background: var(--glass-bg); backdrop-filter: blur(10px); border: 1px solid var(--border-light); border-radius: var(--radius-md); padding: 16px; cursor: pointer; text-align: left; transition: all 0.2s; box-shadow: var(--shadow-sm); }
|
| 145 |
+
.ex-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--accent); }
|
| 146 |
+
.ex-title { font-size: 13px; font-weight: 600; color: var(--text-main); margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
|
| 147 |
+
.ex-desc { font-size: 12px; color: var(--text-muted); line-height: 1.4; }
|
| 148 |
+
|
| 149 |
+
/* Message Bubbles */
|
| 150 |
+
.msg { display: flex; gap: 16px; animation: msgIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; max-width: 85%; }
|
| 151 |
+
@keyframes msgIn { from { opacity: 0; transform: translateY(15px) scale(0.96); } to { opacity: 1; transform: none; } }
|
| 152 |
+
.msg.user { flex-direction: row-reverse; align-self: flex-end; }
|
| 153 |
+
.msg.bot { align-self: flex-start; }
|
| 154 |
+
.avatar { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; overflow: hidden; }
|
| 155 |
+
.msg.user .avatar { background: #f0f0f0; color: var(--text-main); border: 1px solid var(--border-light); }
|
| 156 |
+
.msg.bot .avatar { border: 1px solid var(--border-light); background: #fff; padding: 2px; }
|
| 157 |
+
.msg.bot .avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
|
| 158 |
+
.body { display: flex; flex-direction: column; gap: 6px; max-width: 100%; width: 100%; }
|
| 159 |
+
.msg.user .body { align-items: flex-end; }
|
| 160 |
+
|
| 161 |
+
.bubble { padding: 16px 20px; font-size: 15px; line-height: 1.6; color: var(--text-main); position: relative; word-break: break-word; overflow-wrap: anywhere; }
|
| 162 |
+
.msg.user .bubble { background: var(--text-main); color: #ffffff; border-radius: 20px 20px 4px 20px; box-shadow: var(--shadow-sm); }
|
| 163 |
+
.msg.bot .bubble { background: #ffffff; border: 1px solid var(--border-light); border-radius: 4px 20px 20px 20px; box-shadow: var(--shadow-sm); width: 100%; }
|
| 164 |
+
|
| 165 |
+
/* Bot Actions (Copy & Text to Speech) */
|
| 166 |
+
.msg-actions { display: flex; gap: 8px; margin-top: 4px; padding-left: 12px; opacity: 0.7; transition: opacity 0.2s; flex-wrap: wrap;}
|
| 167 |
+
.msg.bot:hover .msg-actions { opacity: 1; }
|
| 168 |
+
.action-btn { background: var(--bg-secondary); border: 1px solid var(--border-light); color: var(--text-main); font-size: 12px; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 8px; transition: all 0.2s; }
|
| 169 |
+
.action-btn:hover { background: #e5e5ea; transform: translateY(-1px); }
|
| 170 |
+
.action-btn.tts-btn { color: var(--accent-green); }
|
| 171 |
+
|
| 172 |
+
/* Reasoning Box (Hidden as requested) */
|
| 173 |
+
.think-box { display: none; }
|
| 174 |
+
|
| 175 |
+
/* Markdown Formatting */
|
| 176 |
+
.bubble p { margin-bottom: 12px; }
|
| 177 |
+
.bubble p:last-child { margin-bottom: 0; }
|
| 178 |
+
.bubble code { font-family: var(--font-mono); background: rgba(0,0,0,0.05); padding: 3px 6px; border-radius: 6px; font-size: 0.85em; color: #e83e8c; }
|
| 179 |
+
.msg.user .bubble code { background: rgba(255,255,255,0.2); color: #fff; }
|
| 180 |
+
.bubble img { max-width: 100%; border-radius: 12px; margin: 10px 0; box-shadow: var(--shadow-sm); border: 1px solid var(--border-light); }
|
| 181 |
+
.bubble pre { background: #1d1d1f; color: #fff; padding: 16px; border-radius: 12px; overflow-x: auto; margin: 10px 0;}
|
| 182 |
+
.bubble pre code { background: transparent; color: #fff; padding: 0;}
|
| 183 |
+
|
| 184 |
+
/* ==========================================================================
|
| 185 |
+
CODE GENERATING UI (เคจเคฏเคพ เคซเฅเคเคฐ: "Generating Code...")
|
| 186 |
+
========================================================================== */
|
| 187 |
+
.streaming-code-wrapper { background: #1d1d1f; border-radius: 12px; border: 1px solid #333; overflow: hidden; margin: 10px 0; box-shadow: var(--shadow-md); }
|
| 188 |
+
.streaming-code-header { background: #2d2d30; padding: 10px 16px; font-size: 13px; font-weight: 600; color: #fff; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid #444; font-family: var(--font-mono); }
|
| 189 |
+
.loader-spinner { width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #FF9933; border-radius: 50%; animation: spin 1s linear infinite; }
|
| 190 |
+
@keyframes spin { to { transform: rotate(360deg); } }
|
| 191 |
+
.streaming-code-wrapper pre { margin: 0; background: transparent; border-radius: 0; }
|
| 192 |
+
|
| 193 |
+
/* Final Workspace Trigger Card */
|
| 194 |
+
.ws-trigger-card { background: var(--bg-secondary); border: 1px solid var(--border-light); border-radius: 12px; padding: 16px; margin: 12px 0; display: flex; align-items: center; justify-content: space-between; box-shadow: inset 0 1px 2px rgba(255,255,255,0.5); }
|
| 195 |
+
.ws-trigger-info { display: flex; align-items: center; gap: 12px; }
|
| 196 |
+
.ws-trigger-icon { width: 44px; height: 44px; border-radius: 10px; background: #fff; display: flex; align-items: center; justify-content: center; font-size: 22px; box-shadow: var(--shadow-sm); border: 1px solid var(--border-light); }
|
| 197 |
+
.ws-trigger-text h4 { font-size: 14px; font-weight: 600; margin-bottom: 2px; }
|
| 198 |
+
.ws-trigger-text p { font-size: 12px; color: var(--text-muted); font-family: var(--font-mono); text-transform: uppercase; }
|
| 199 |
+
.ws-btn { padding: 8px 16px; background: var(--text-main); color: #fff; border: none; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
|
| 200 |
+
.ws-btn:hover { transform: scale(1.05); background: var(--accent); }
|
| 201 |
|
| 202 |
/* Typing & Cursor */
|
| 203 |
+
.blinking-cursor { display: inline-block; width: 8px; height: 16px; background: var(--accent); margin-left: 4px; animation: blink 1s step-end infinite; vertical-align: middle; }
|
| 204 |
@keyframes blink { 50% { opacity: 0; } }
|
| 205 |
+
.typing { display: flex; align-items: center; gap: 6px; padding: 4px; }
|
| 206 |
+
.typing span { width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); opacity: 0.4; animation: bounce 1.4s infinite ease-in-out both; }
|
| 207 |
+
.typing span:nth-child(1) { animation-delay: -0.32s; }
|
| 208 |
+
.typing span:nth-child(2) { animation-delay: -0.16s; }
|
| 209 |
+
@keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
|
| 210 |
+
|
| 211 |
+
/* Scroll to Bottom */
|
| 212 |
+
.scroll-bottom-btn { position: absolute; bottom: 120px; left: 50%; width: 44px; height: 44px; border-radius: 50%; background: var(--glass-bg); backdrop-filter: blur(10px); border: 1px solid var(--border-light); box-shadow: var(--shadow-md); display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 18px; color: var(--text-main); opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(20px); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); z-index: 20; }
|
| 213 |
+
.scroll-bottom-btn.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
|
| 214 |
+
.scroll-bottom-btn:hover { background: #fff; transform: translateX(-50%) translateY(-3px); box-shadow: var(--shadow-lg); }
|
| 215 |
+
|
| 216 |
+
/* ==========================================================================
|
| 217 |
+
INPUT AREA & ATTACHMENT MENU (เคเคจเคชเฅเค เคเคฐ เคซเคพเคเคฒ เค
เคเฅเคเคฎเฅเคเค)
|
| 218 |
+
========================================================================== */
|
| 219 |
+
.input-container { flex-shrink: 0; padding: 10px 40px 24px; background: var(--bg-color); border-top: 1px solid var(--border-light); position: relative; z-index: 20; }
|
| 220 |
+
.input-wrap { display: flex; flex-direction: column; background: var(--glass-bg); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid var(--border-light); border-radius: 24px; box-shadow: var(--shadow-md); transition: box-shadow 0.3s, border-color 0.3s; }
|
| 221 |
+
.input-wrap:focus-within { border-color: var(--border-focus); box-shadow: 0 12px 40px rgba(0,0,0,0.1), 0 0 0 3px rgba(255,153,51,0.1); }
|
| 222 |
+
|
| 223 |
+
.img-prev-inline { display: none; padding: 12px 20px 0; align-items: center; gap: 12px; }
|
| 224 |
+
.img-prev-inline.show { display: flex; animation: fadeIn 0.3s; }
|
| 225 |
+
.inline-th { width: 40px; height: 40px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border-light); display: flex; justify-content: center; align-items: center; background: #f0f0f0; font-size: 20px; }
|
| 226 |
+
.inline-info { flex: 1; }
|
| 227 |
+
.inline-nm { font-size: 12px; font-weight: 600; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
|
| 228 |
+
.inline-rm { background: rgba(0,0,0,0.05); border: none; width: 24px; height: 24px; border-radius: 50%; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center; transition: background 0.2s; }
|
| 229 |
+
.inline-rm:hover { background: #ff3b30; color: #fff; }
|
| 230 |
+
|
| 231 |
+
.input-row { display: flex; align-items: flex-end; gap: 12px; padding: 12px 16px 12px 20px; }
|
| 232 |
+
.chat-ta { flex: 1; border: none; outline: none; background: transparent; font-family: var(--font-sans); font-size: 15px; color: var(--text-main); line-height: 1.5; resize: none; max-height: 150px; min-height: 24px; padding: 8px 0; }
|
| 233 |
+
.chat-ta::placeholder { color: var(--text-muted); }
|
| 234 |
+
|
| 235 |
+
/* Gemini Style Attachment Menu */
|
| 236 |
.attach-wrapper { position: relative; }
|
| 237 |
+
.attach-btn { width: 38px; height: 38px; border-radius: 12px; background: transparent; border: 1px solid transparent; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--text-muted); transition: all 0.2s; position: relative; overflow: hidden; padding-bottom: 4px; }
|
| 238 |
+
.attach-btn:hover { background: rgba(0,0,0,0.04); color: var(--text-main); }
|
| 239 |
+
.attach-btn svg { width: 20px; height: 20px; }
|
| 240 |
+
.attach-menu { position: absolute; bottom: 50px; left: 0; background: rgba(255,255,255,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--border-light); border-radius: 16px; padding: 8px; box-shadow: var(--shadow-lg); display: none; flex-direction: column; gap: 4px; z-index: 100; min-width: 160px; transform: translateY(10px); opacity: 0; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); }
|
| 241 |
.attach-menu.show { display: flex; transform: translateY(0); opacity: 1; }
|
| 242 |
.attach-menu-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; cursor: pointer; font-size: 14px; font-weight: 500; color: var(--text-main); transition: background 0.2s; }
|
| 243 |
.attach-menu-item:hover { background: rgba(0,0,0,0.05); }
|
|
|
|
| 249 |
.voice-btn.recording { color: #ff3b30; animation: pulseRecord 1.5s infinite; }
|
| 250 |
@keyframes pulseRecord { 0% { background: rgba(255,59,48,0.1); } 50% { background: rgba(255,59,48,0.3); } 100% { background: rgba(255,59,48,0.1); } }
|
| 251 |
|
| 252 |
+
.send-btn { width: 42px; height: 42px; border-radius: 14px; border: none; background: linear-gradient(135deg, #FF9933 0%, #ffffff 50%, #138808 100%); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
| 253 |
+
.send-btn svg { width: 18px; height: 18px; fill: #1d1d1f; transition: transform 0.2s; }
|
| 254 |
+
.send-btn:hover { box-shadow: 0 6px 16px rgba(255,153,51,0.3); transform: translate(2px, -2px); }
|
| 255 |
+
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(100%); transform: none; }
|
| 256 |
+
.input-hint { text-align: center; font-size: 11px; color: var(--text-muted); margin-top: 12px; font-weight: 500; }
|
| 257 |
+
|
| 258 |
+
/* ==========================================================================
|
| 259 |
+
WORKSPACE PANEL (เคเฅเคก เคตเคฐเฅเคเคธเฅเคชเฅเคธ)
|
| 260 |
+
========================================================================== */
|
| 261 |
+
.ws-hdr { padding: 14px 20px; border-bottom: 1px solid var(--border-light); background: #fff; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
| 262 |
+
.ws-title { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
| 263 |
+
.ws-actions { display: flex; gap: 8px; }
|
| 264 |
+
.ws-icon-btn { padding: 6px 12px; border-radius: 8px; background: var(--bg-secondary); border: 1px solid var(--border-light); cursor: pointer; display: flex; align-items: center; gap: 6px; color: var(--text-main); transition: all 0.2s; font-size: 12px; font-weight: 600; }
|
| 265 |
+
.ws-icon-btn:hover { background: #e5e5ea; }
|
| 266 |
+
.ws-close { padding: 6px; background: transparent; border-color: transparent; }
|
| 267 |
+
.ws-close:hover { color: #ff3b30; background: #fff0f0; }
|
| 268 |
+
.ws-content { flex: 1; overflow-y: auto; background: #fafafa; padding: 20px; }
|
| 269 |
+
/* Modified workspace pre styling to match Highlight.js dark theme better if needed,
|
| 270 |
+
but since background is #fafafa, we need a dark box for code */
|
| 271 |
+
.ws-content pre { margin: 0; padding: 20px; background: #0d1117; border: 1px solid var(--border-light); border-radius: 12px; font-family: var(--font-mono); font-size: 14px; line-height: 1.6; overflow-x: auto; box-shadow: var(--shadow-sm); color: #c9d1d9;}
|
| 272 |
+
|
| 273 |
+
/* ==========================================================================
|
| 274 |
+
AUTH MODAL (เคฒเฅเคเคฟเคจ เคธเคฟเคธเฅเคเคฎ)
|
| 275 |
+
========================================================================== */
|
| 276 |
.modal-overlay { position: fixed; inset: 0; background: rgba(255,255,255,0.85); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 5000; display: none; justify-content: center; align-items: center; animation: fadeIn 0.3s ease; }
|
| 277 |
.modal-card { background: #fff; padding: 32px 36px; border-radius: 24px; width: 90%; max-width: 420px; border: 1px solid var(--border-light); box-shadow: var(--shadow-lg); position: relative; }
|
| 278 |
+
.close-modal { position: absolute; top: 20px; right: 20px; background: none; border: none; font-size: 24px; color: var(--text-muted); cursor: pointer; transition: 0.2s; }
|
| 279 |
.close-modal:hover { color: var(--text-main); transform: rotate(90deg); }
|
| 280 |
.auth-header { text-align: center; margin-bottom: 24px; }
|
| 281 |
.auth-header h2 { font-family: var(--font-sans); font-size: 24px; font-weight: 800; color: var(--text-main); }
|
|
|
|
| 292 |
.secondary-btn { width: 100%; padding: 12px; background: transparent; color: var(--text-muted); border: none; font-size: 14px; font-weight: 600; cursor: pointer; margin-top: 4px; }
|
| 293 |
.secondary-btn:hover { color: var(--text-main); }
|
| 294 |
|
| 295 |
+
/* ==========================================================================
|
| 296 |
+
MOBILE RESPONSIVENESS (เคฎเฅเคฌเคพเคเคฒ เคฒเฅเคเคเค)
|
| 297 |
+
========================================================================== */
|
| 298 |
@media(max-width:850px){
|
| 299 |
+
.sidebar { position: absolute; left: -280px; z-index: 100; height: 100%; transition: left 0.3s ease; }
|
| 300 |
+
.sidebar.show { left: 0; box-shadow: var(--shadow-lg); }
|
| 301 |
.mobile-menu-btn { display: block; }
|
| 302 |
.close-sidebar-btn { display: block; }
|
| 303 |
+
.workspace.open { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; }
|
| 304 |
+
.messages { padding: 20px; }
|
| 305 |
+
.input-container { padding: 10px 15px 15px; }
|
| 306 |
+
.msg { max-width: 95%; }
|
| 307 |
+
.scroll-bottom-btn { bottom: 110px; }
|
| 308 |
}
|
| 309 |
</style>
|
| 310 |
</head>
|
| 311 |
<body>
|
| 312 |
|
| 313 |
+
<!-- TOAST NOTIFICATION -->
|
| 314 |
<div id="toast-container"></div>
|
| 315 |
|
| 316 |
+
<!-- GET STARTED WELCOME OVERLAY -->
|
| 317 |
<div class="welcome-overlay" id="welcomeOverlay">
|
| 318 |
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo">
|
| 319 |
<h1>Welcome to Arjun 2.O</h1>
|
|
|
|
| 321 |
<button class="start-btn" onclick="startArjunApp()">Get Started</button>
|
| 322 |
</div>
|
| 323 |
|
| 324 |
+
<!-- BACKGROUND ANIMATION -->
|
| 325 |
<div class="bg"><div class="orb orb1"></div><div class="orb orb2"></div></div>
|
| 326 |
|
| 327 |
<div class="shell">
|
| 328 |
+
<!-- SIDEBAR -->
|
| 329 |
<aside class="sidebar" id="sidebar">
|
| 330 |
<div class="logo-area">
|
| 331 |
<div class="logo-row">
|
|
|
|
| 357 |
</div>
|
| 358 |
</aside>
|
| 359 |
|
| 360 |
+
<!-- MAIN CHAT AREA -->
|
| 361 |
<div class="main-wrapper">
|
| 362 |
<main class="chat-main" id="chatMain">
|
| 363 |
<header class="chat-hdr">
|
|
|
|
| 370 |
</header>
|
| 371 |
|
| 372 |
<div class="messages" id="msgs" onscroll="checkScroll()">
|
| 373 |
+
<!-- Welcome Screen inside Chat -->
|
| 374 |
<div class="welcome" id="welcome">
|
| 375 |
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo" class="welcome-logo">
|
| 376 |
<div class="welcome-title">Welcome, User!</div>
|
|
|
|
| 393 |
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
|
| 394 |
</button>
|
| 395 |
|
| 396 |
+
<!-- INPUT CONTAINER -->
|
| 397 |
<div class="input-container">
|
| 398 |
<div class="input-wrap">
|
| 399 |
<div class="img-prev-inline" id="imgPrev">
|
|
|
|
| 424 |
<input type="file" id="inp-image" class="hidden-file-input" accept="image/*" onchange="handleSpecificFile(this, 'image')">
|
| 425 |
<input type="file" id="inp-video" class="hidden-file-input" accept="video/*" onchange="handleSpecificFile(this, 'video')">
|
| 426 |
<input type="file" id="inp-audio" class="hidden-file-input" accept="audio/*" onchange="handleSpecificFile(this, 'audio')">
|
| 427 |
+
<!-- Accept all major document and code extensions -->
|
| 428 |
+
<input type="file" id="inp-document" class="hidden-file-input" accept=".pdf,.doc,.docx,.txt,.md,.csv,.json,.py,.js,.html,.css,.cpp,.c,.java" onchange="handleSpecificFile(this, 'document')">
|
| 429 |
</div>
|
| 430 |
|
| 431 |
+
<!-- Voice Input Mic (Smart Punctuation & Hindi/English) -->
|
| 432 |
<button class="attach-btn voice-btn" id="voiceBtn" onclick="toggleVoice()" title="Voice Input">
|
| 433 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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" x2="12" y1="19" y2="22"></line></svg>
|
| 434 |
</button>
|
|
|
|
| 444 |
</div>
|
| 445 |
</main>
|
| 446 |
|
| 447 |
+
<!-- WORKSPACE PANEL -->
|
| 448 |
<aside class="workspace" id="workspace">
|
| 449 |
<div class="ws-hdr">
|
| 450 |
<div class="ws-title"><span>๐ป</span><span id="wsTitleText">Generated Code</span></div>
|
| 451 |
<div class="ws-actions">
|
| 452 |
+
<button class="ws-icon-btn" onclick="copyWorkspaceCode(this)" id="wsCopyBtn">๐ Copy</button>
|
| 453 |
<button class="ws-icon-btn ws-close" onclick="closeWorkspace()" title="Close">โ</button>
|
| 454 |
</div>
|
| 455 |
</div>
|
| 456 |
<div class="ws-content">
|
| 457 |
+
<!-- Syntax highlighted code block will appear here -->
|
| 458 |
<pre><code id="wsCodeBlock" class="hljs"></code></pre>
|
| 459 |
</div>
|
| 460 |
</aside>
|
| 461 |
</div>
|
| 462 |
</div>
|
| 463 |
|
| 464 |
+
<!-- AUTH MODAL (GAS) -->
|
| 465 |
<div class="modal-overlay" id="authModal">
|
| 466 |
<div class="modal-card">
|
| 467 |
<button class="close-modal" onclick="closeAuthModal()">โ</button>
|
|
|
|
| 470 |
<div class="auth-tab active" id="tab-login" onclick="switchAuthTab('login')">Login</div>
|
| 471 |
<div class="auth-tab" id="tab-register" onclick="switchAuthTab('register')">Register</div>
|
| 472 |
</div>
|
| 473 |
+
<!-- Login Form -->
|
| 474 |
<div id="flow-login">
|
| 475 |
<div class="auth-step active" id="log-step-1">
|
| 476 |
<div class="input-group"><input type="email" id="logEmail" placeholder="Registered Email"></div>
|
|
|
|
| 482 |
<button class="secondary-btn" onclick="switchAuthStep('log-step-2', 'log-step-1')">Back</button>
|
| 483 |
</div>
|
| 484 |
</div>
|
| 485 |
+
<!-- Register Form -->
|
| 486 |
<div id="flow-register" style="display:none;">
|
| 487 |
<div class="auth-step active" id="reg-step-1">
|
| 488 |
<div class="input-group"><input type="text" id="regName" placeholder="Full Name"></div>
|
|
|
|
| 499 |
</div>
|
| 500 |
|
| 501 |
<script>
|
| 502 |
+
/* ==========================================================================
|
| 503 |
+
GLOBAL VARIABLES & CONFIGURATION (เคตเฅเคถเฅเคตเคฟเค เคเคฐ)
|
| 504 |
+
========================================================================== */
|
| 505 |
+
// PDF.JS Worker setup for PDF extraction
|
| 506 |
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
| 507 |
|
| 508 |
const GAS_URL = "https://script.google.com/macros/s/AKfycbzTsXGoVh97JjtFAAG825GF4WUZqf7FDB3Zbxyf0nrLXt_1HjJSSqA829UcdoamRPvg0A/exec";
|
|
|
|
| 512 |
let guestCount = parseInt(localStorage.getItem('arjun_guest') || '0');
|
| 513 |
window.isStreamingGlobalFlag = false;
|
| 514 |
|
| 515 |
+
// State management object
|
| 516 |
+
const S = {
|
| 517 |
+
history: [],
|
| 518 |
+
pending: null,
|
| 519 |
+
busy: false,
|
| 520 |
+
isUserScrolling: false,
|
| 521 |
+
codeBlocks: [],
|
| 522 |
+
currentWorkspaceId: null
|
| 523 |
+
};
|
| 524 |
|
| 525 |
+
/* ==========================================================================
|
| 526 |
+
INITIALIZATION (เคถเฅเคฐเฅเคเคคเฅ เคชเฅเคฐเคเฅเคฐเคฟเคฏเคพ)
|
| 527 |
+
========================================================================== */
|
| 528 |
document.addEventListener('DOMContentLoaded', () => {
|
| 529 |
+
// Show welcome overlay only once
|
| 530 |
+
if(!localStorage.getItem('arjun_welcome_seen')) {
|
| 531 |
+
document.getElementById('welcomeOverlay').style.display = 'flex';
|
| 532 |
+
} else {
|
| 533 |
+
document.getElementById('welcomeOverlay').style.display = 'none';
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
updateAuthUI();
|
| 537 |
+
|
| 538 |
+
// Fetch cloud history if user is logged in
|
| 539 |
+
if(currentUser) {
|
| 540 |
+
fetchHistoryFromGAS();
|
| 541 |
+
} else {
|
| 542 |
+
document.getElementById('guestBadge').style.display = 'inline-block';
|
| 543 |
+
document.getElementById('guestCountDisplay').innerText = guestCount;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
// Initialize Speech Synthesis Voices (so it's ready when needed)
|
| 547 |
+
if('speechSynthesis' in window) {
|
| 548 |
+
window.speechSynthesis.getVoices();
|
| 549 |
+
window.speechSynthesis.onvoiceschanged = function() {
|
| 550 |
+
window.speechSynthesis.getVoices();
|
| 551 |
+
};
|
| 552 |
+
}
|
| 553 |
});
|
| 554 |
|
| 555 |
function startArjunApp() {
|
|
|
|
| 560 |
}
|
| 561 |
|
| 562 |
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('show'); }
|
| 563 |
+
|
| 564 |
function showToast(msg) {
|
| 565 |
const container = document.getElementById('toast-container');
|
| 566 |
const t = document.createElement('div'); t.className = 'toast'; t.innerText = msg;
|
| 567 |
+
container.appendChild(t);
|
| 568 |
+
setTimeout(() => t.remove(), 3000);
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
/* ==========================================================================
|
| 572 |
+
ROBUST COPY FUNCTIONALITY (เคฎเคเคฌเฅเคค เคเฅเคช๏ฟฝ๏ฟฝ๏ฟฝ เคซเคเคเฅเคถเคจ - NO COMPROMISE)
|
| 573 |
+
========================================================================== */
|
| 574 |
+
// Fallback method for devices/browsers that restrict navigator.clipboard
|
| 575 |
+
function fallbackCopyTextToClipboard(text, btn) {
|
| 576 |
+
var textArea = document.createElement("textarea");
|
| 577 |
+
textArea.value = text;
|
| 578 |
+
// Avoid scrolling to bottom
|
| 579 |
+
textArea.style.top = "0";
|
| 580 |
+
textArea.style.left = "0";
|
| 581 |
+
textArea.style.position = "fixed";
|
| 582 |
+
|
| 583 |
+
document.body.appendChild(textArea);
|
| 584 |
+
textArea.focus();
|
| 585 |
+
textArea.select();
|
| 586 |
+
|
| 587 |
+
try {
|
| 588 |
+
var successful = document.execCommand('copy');
|
| 589 |
+
if(successful) {
|
| 590 |
+
const orig = btn.innerHTML;
|
| 591 |
+
btn.innerHTML = "โ Copied";
|
| 592 |
+
setTimeout(() => btn.innerHTML = orig, 2000);
|
| 593 |
+
} else {
|
| 594 |
+
showToast("Failed to copy using fallback!");
|
| 595 |
+
}
|
| 596 |
+
} catch (err) {
|
| 597 |
+
showToast("Error copying text!");
|
| 598 |
+
console.error('Fallback: Oops, unable to copy', err);
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
document.body.removeChild(textArea);
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
// Main copy function called from UI
|
| 605 |
+
function copyText(btn, text) {
|
| 606 |
+
// If modern clipboard API is not available or we are in restricted iframe
|
| 607 |
+
if (!navigator.clipboard) {
|
| 608 |
+
fallbackCopyTextToClipboard(text, btn);
|
| 609 |
+
return;
|
| 610 |
+
}
|
| 611 |
+
// Try modern API
|
| 612 |
+
navigator.clipboard.writeText(text).then(function() {
|
| 613 |
+
const orig = btn.innerHTML;
|
| 614 |
+
btn.innerHTML = "โ Copied";
|
| 615 |
+
setTimeout(() => btn.innerHTML = orig, 2000);
|
| 616 |
+
}, function(err) {
|
| 617 |
+
// If modern API fails (e.g. permission denied), use fallback
|
| 618 |
+
console.warn('Async: Could not copy text, using fallback: ', err);
|
| 619 |
+
fallbackCopyTextToClipboard(text, btn);
|
| 620 |
+
});
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
function copyWorkspaceCode(btn) {
|
| 624 |
+
if(S.currentWorkspaceId !== null) {
|
| 625 |
+
const codeText = S.codeBlocks[S.currentWorkspaceId].code;
|
| 626 |
+
copyText(btn, codeText);
|
| 627 |
+
}
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
/* ==========================================================================
|
| 631 |
+
TEXT TO SPEECH: MICROSOFT EDGE (Prabhat Neural)
|
| 632 |
+
========================================================================== */
|
| 633 |
+
function speakText(text) {
|
| 634 |
+
if (!('speechSynthesis' in window)) {
|
| 635 |
+
showToast("Text-to-Speech not supported in this browser.");
|
| 636 |
+
return;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
// Cancel any ongoing speech
|
| 640 |
+
window.speechSynthesis.cancel();
|
| 641 |
+
|
| 642 |
+
// Clean markdown symbols from text before speaking
|
| 643 |
+
let cleanText = text.replace(/[*_~`#]/g, '').replace(/<[^>]*>?/gm, '');
|
| 644 |
+
|
| 645 |
+
let utterance = new SpeechSynthesisUtterance(cleanText);
|
| 646 |
+
|
| 647 |
+
// Default language setting
|
| 648 |
+
utterance.lang = 'hi-IN';
|
| 649 |
+
utterance.rate = 1.0;
|
| 650 |
+
utterance.pitch = 1.0;
|
| 651 |
+
|
| 652 |
+
let voices = window.speechSynthesis.getVoices();
|
| 653 |
+
|
| 654 |
+
// 1. Search for Microsoft Prabhat Neural explicitly
|
| 655 |
+
let bestVoice = voices.find(v => v.name.includes('Prabhat') && v.name.includes('Neural'));
|
| 656 |
+
|
| 657 |
+
// 2. Fallback: Search for any Hindi Neural voice
|
| 658 |
+
if(!bestVoice) {
|
| 659 |
+
bestVoice = voices.find(v => v.lang.includes('hi') && v.name.includes('Neural'));
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
// 3. Fallback: Any Hindi Voice
|
| 663 |
+
if(!bestVoice) {
|
| 664 |
+
bestVoice = voices.find(v => v.lang.includes('hi-IN'));
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
if (bestVoice) {
|
| 668 |
+
utterance.voice = bestVoice;
|
| 669 |
+
console.log("Using TTS Voice:", bestVoice.name);
|
| 670 |
+
} else {
|
| 671 |
+
console.log("Prabhat/Hindi voice not found. Using default system voice.");
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
window.speechSynthesis.speak(utterance);
|
| 675 |
+
showToast("๐ Playing Audio...");
|
| 676 |
}
|
| 677 |
|
| 678 |
+
/* ==========================================================================
|
| 679 |
+
SMART VOICE RECOGNITION (เคตเฅเคเคธ เคเคพเคเคชเคฟเคเค)
|
| 680 |
+
========================================================================== */
|
| 681 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 682 |
+
let recognition = null;
|
| 683 |
+
let isRecording = false;
|
| 684 |
|
| 685 |
if (SpeechRecognition) {
|
| 686 |
recognition = new SpeechRecognition();
|
| 687 |
recognition.continuous = false;
|
| 688 |
recognition.interimResults = false;
|
| 689 |
|
| 690 |
+
// 'hi-IN' correctly captures pure Hindi in Devanagari and English words dynamically.
|
| 691 |
recognition.lang = 'hi-IN';
|
| 692 |
|
| 693 |
+
recognition.onstart = () => {
|
| 694 |
+
isRecording = true;
|
| 695 |
+
document.getElementById('voiceBtn').classList.add('recording');
|
| 696 |
+
};
|
| 697 |
+
|
| 698 |
recognition.onresult = (event) => {
|
| 699 |
let transcript = '';
|
| 700 |
+
for (let i = event.resultIndex; i < event.results.length; ++i) {
|
| 701 |
+
transcript += event.results[i][0].transcript;
|
| 702 |
+
}
|
| 703 |
transcript = transcript.trim();
|
| 704 |
if(transcript.length > 0) {
|
| 705 |
+
// Capitalize first letter
|
| 706 |
transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
|
| 707 |
+
// Add punctuation if missing
|
| 708 |
+
if (!/[.!?เฅค]$/.test(transcript)) transcript += '.';
|
| 709 |
+
transcript += ' ';
|
| 710 |
}
|
| 711 |
const inp = document.getElementById('chatInput');
|
| 712 |
inp.value = (inp.value + ' ' + transcript).trim();
|
| 713 |
autoGrow(inp);
|
| 714 |
};
|
| 715 |
+
|
| 716 |
+
recognition.onend = () => {
|
| 717 |
+
isRecording = false;
|
| 718 |
+
document.getElementById('voiceBtn').classList.remove('recording');
|
| 719 |
+
};
|
| 720 |
+
|
| 721 |
+
recognition.onerror = (event) => {
|
| 722 |
+
showToast("Voice recognition failed: " + event.error);
|
| 723 |
+
isRecording = false;
|
| 724 |
+
document.getElementById('voiceBtn').classList.remove('recording');
|
| 725 |
+
};
|
| 726 |
}
|
| 727 |
+
|
| 728 |
function toggleVoice() {
|
| 729 |
if(!recognition) return showToast("Voice input not supported in this browser.");
|
| 730 |
if(isRecording) recognition.stop(); else recognition.start();
|
| 731 |
}
|
| 732 |
|
| 733 |
+
/* ==========================================================================
|
| 734 |
+
AUTHENTICATION & HISTORY (เคฒเฅเคเคฟเคจ เคเคฐ เคกเฅเคเคพ เคธเคฟเคเค)
|
| 735 |
+
========================================================================== */
|
| 736 |
+
function updateAuthUI() {
|
| 737 |
+
if (currentUser) {
|
| 738 |
+
document.getElementById('loginPromptBtn').style.display = 'none';
|
| 739 |
+
document.getElementById('userProfile').style.display = 'flex';
|
| 740 |
+
document.getElementById('uEmail').innerText = currentUser;
|
| 741 |
+
document.getElementById('uAvatar').innerText = currentUser.charAt(0).toUpperCase();
|
| 742 |
+
document.getElementById('guestBadge').style.display = 'none';
|
| 743 |
+
} else {
|
| 744 |
+
document.getElementById('loginPromptBtn').style.display = 'flex';
|
| 745 |
+
document.getElementById('userProfile').style.display = 'none';
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
function openAuthModal() { document.getElementById('authModal').style.display = 'flex'; }
|
| 750 |
function closeAuthModal() { document.getElementById('authModal').style.display = 'none'; }
|
| 751 |
+
function switchAuthTab(tab) {
|
| 752 |
+
document.querySelectorAll('.auth-tab').forEach(e => e.classList.remove('active'));
|
| 753 |
+
document.getElementById(`tab-${tab}`).classList.add('active');
|
| 754 |
+
document.getElementById('flow-login').style.display = tab === 'login' ? 'block' : 'none';
|
| 755 |
+
document.getElementById('flow-register').style.display = tab === 'register' ? 'block' : 'none';
|
| 756 |
+
}
|
| 757 |
+
function switchAuthStep(curr, next) {
|
| 758 |
+
document.getElementById(curr).classList.remove('active');
|
| 759 |
+
document.getElementById(next).classList.add('active');
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
async function processAuth(action) {
|
| 763 |
+
let payload = { action }; let btnId = '';
|
| 764 |
+
if (action === 'register_send_otp') {
|
| 765 |
+
payload.name = document.getElementById('regName').value.trim();
|
| 766 |
+
payload.email = document.getElementById('regEmail').value.trim();
|
| 767 |
+
payload.phone = "0000000000"; payload.organization = "Arjun Compound";
|
| 768 |
+
if(!payload.email || !payload.name) return showToast("Name and Email required.");
|
| 769 |
+
btnId = 'btn-reg-otp';
|
| 770 |
+
} else if (action === 'login_send_otp') {
|
| 771 |
+
payload.email = document.getElementById('logEmail').value.trim();
|
| 772 |
+
if(!payload.email) return showToast("Enter email first.");
|
| 773 |
+
btnId = 'btn-log-otp';
|
| 774 |
+
} else if (action === 'register_verify' || action === 'login_verify') {
|
| 775 |
+
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
|
| 776 |
+
payload.otp = document.getElementById(action === 'register_verify' ? 'regOTP' : 'logOTP').value.trim();
|
| 777 |
+
if(!payload.otp) return showToast("Enter OTP!");
|
| 778 |
+
btnId = action === 'register_verify' ? 'btn-reg-verify' : 'btn-log-verify';
|
| 779 |
+
}
|
| 780 |
+
const btn = document.getElementById(btnId); const ogText = btn.innerHTML;
|
| 781 |
+
btn.disabled = true; btn.innerText = 'Processing...';
|
| 782 |
+
try {
|
| 783 |
+
const res = await fetch(GAS_URL, { method: 'POST', body: JSON.stringify(payload) });
|
| 784 |
+
const data = await res.json();
|
| 785 |
+
if (data.status === 'success') {
|
| 786 |
+
showToast(data.message);
|
| 787 |
+
if (action === 'register_send_otp') switchAuthStep('reg-step-1', 'reg-step-2');
|
| 788 |
+
else if (action === 'login_send_otp') switchAuthStep('log-step-1', 'log-step-2');
|
| 789 |
+
else if (action === 'register_verify' || action === 'login_verify') {
|
| 790 |
+
currentUser = payload.email;
|
| 791 |
+
localStorage.setItem('arjun_user', currentUser);
|
| 792 |
+
closeAuthModal(); updateAuthUI(); fetchHistoryFromGAS();
|
| 793 |
+
}
|
| 794 |
+
} else {
|
| 795 |
+
showToast(data.message);
|
| 796 |
+
}
|
| 797 |
+
} catch (e) {
|
| 798 |
+
showToast("Network Error.");
|
| 799 |
+
}
|
| 800 |
+
btn.disabled = false; btn.innerHTML = ogText;
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
function logout() { localStorage.removeItem('arjun_user'); location.reload(); }
|
| 804 |
+
async function clearMemory() {
|
| 805 |
+
if(currentUser) {
|
| 806 |
+
showToast("Clearing Cloud History...");
|
| 807 |
+
await fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "delete_history", email: currentUser }) });
|
| 808 |
+
}
|
| 809 |
+
location.reload();
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
async function fetchHistoryFromGAS() {
|
| 813 |
+
const m = document.getElementById('msgs');
|
| 814 |
+
document.getElementById('welcome').style.display = 'none';
|
| 815 |
+
m.innerHTML = '<div style="text-align:center; padding:40px; color:var(--text-muted);">Syncing Secure Data...</div>';
|
| 816 |
+
try {
|
| 817 |
+
const res = await fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "get_history", email: currentUser }) });
|
| 818 |
+
const data = await res.json();
|
| 819 |
+
m.innerHTML = '';
|
| 820 |
+
if (data.status === 'success' && data.historyJSON !== "[]") {
|
| 821 |
+
S.history = JSON.parse(data.historyJSON);
|
| 822 |
+
S.history.forEach(msg => {
|
| 823 |
+
if(msg.role === 'user') appendUserDOM(msg.content, msg.badge);
|
| 824 |
+
else {
|
| 825 |
+
window.isStreamingGlobalFlag = false;
|
| 826 |
+
appendBotDOM(msg.content, true);
|
| 827 |
+
}
|
| 828 |
+
});
|
| 829 |
+
setTimeout(forceScrollDown, 100);
|
| 830 |
+
} else {
|
| 831 |
+
document.getElementById('welcome').style.display = 'flex';
|
| 832 |
+
}
|
| 833 |
+
} catch(e) {
|
| 834 |
+
m.innerHTML = ''; document.getElementById('welcome').style.display = 'flex';
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
function syncHistory() {
|
| 839 |
+
if(currentUser) {
|
| 840 |
+
fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "save_history", email: currentUser, historyJSON: JSON.stringify(S.history) }) });
|
| 841 |
+
}
|
| 842 |
+
}
|
| 843 |
|
| 844 |
+
/* ==========================================================================
|
| 845 |
+
ATTACHMENT SYSTEM (เคฎเคฒเฅเคเฅเคฎเฅเคกเคฒ เคซเคพเคเคฒ เคธเคชเฅเคฐเฅเค)
|
| 846 |
+
========================================================================== */
|
| 847 |
function toggleAttachMenu() {
|
| 848 |
const menu = document.getElementById('attachMenu');
|
| 849 |
menu.classList.toggle('show');
|
| 850 |
}
|
| 851 |
+
|
| 852 |
+
// Close attachment menu if clicked outside
|
| 853 |
document.addEventListener('click', function(event) {
|
| 854 |
const wrapper = document.querySelector('.attach-wrapper');
|
| 855 |
+
if (!wrapper.contains(event.target)) {
|
| 856 |
+
document.getElementById('attachMenu').classList.remove('show');
|
| 857 |
+
}
|
| 858 |
});
|
| 859 |
|
| 860 |
+
// Trigger hidden file inputs
|
| 861 |
function triggerInput(type) {
|
| 862 |
document.getElementById('attachMenu').classList.remove('show');
|
| 863 |
document.getElementById('inp-' + type).click();
|
|
|
|
| 868 |
const fileName = file.name.toLowerCase();
|
| 869 |
const fileMime = file.type;
|
| 870 |
|
| 871 |
+
// 1. STRICT FORMAT VALIDATION (เคธเคเฅเคค เคเคพเคเค)
|
| 872 |
+
if (expectedType === 'image' && !fileMime.startsWith('image/')) {
|
| 873 |
+
showToast("Invalid Format! Please select an Image file."); input.value=''; return;
|
| 874 |
+
}
|
| 875 |
+
if (expectedType === 'video' && !fileMime.startsWith('video/')) {
|
| 876 |
+
showToast("Invalid Format! Please select a Video file."); input.value=''; return;
|
| 877 |
+
}
|
| 878 |
+
if (expectedType === 'audio' && !fileMime.startsWith('audio/')) {
|
| 879 |
+
showToast("Invalid Format! Please select an Audio file."); input.value=''; return;
|
| 880 |
+
}
|
| 881 |
|
| 882 |
+
// Setup UI for file preview
|
| 883 |
document.getElementById('imgThumb').style.display = 'none';
|
| 884 |
document.getElementById('fileIcon').style.display = 'none';
|
| 885 |
document.getElementById('imgName').textContent = file.name;
|
|
|
|
| 889 |
|
| 890 |
const reader = new FileReader();
|
| 891 |
|
| 892 |
+
// 2. PROCESS IMAGE
|
| 893 |
if (expectedType === 'image') {
|
| 894 |
reader.onload = e => {
|
| 895 |
S.pending = { type: 'image', data: e.target.result.split(',')[1], name: file.name };
|
|
|
|
| 898 |
};
|
| 899 |
reader.readAsDataURL(file);
|
| 900 |
}
|
| 901 |
+
// 3. PROCESS VIDEO / AUDIO (Send directly as Base64 to Model API)
|
| 902 |
else if (expectedType === 'video' || expectedType === 'audio') {
|
| 903 |
reader.onload = e => {
|
| 904 |
S.pending = { type: expectedType, data: e.target.result.split(',')[1], name: file.name };
|
|
|
|
| 907 |
};
|
| 908 |
reader.readAsDataURL(file);
|
| 909 |
}
|
| 910 |
+
// 4. PROCESS DOCUMENTS & CODE (Extract Text)
|
| 911 |
else if (expectedType === 'document') {
|
| 912 |
document.getElementById('fileIcon').textContent = '๐';
|
| 913 |
document.getElementById('fileIcon').style.display = 'flex';
|
| 914 |
|
| 915 |
+
// A. PDF Extraction
|
| 916 |
if (fileName.endsWith('.pdf')) {
|
| 917 |
reader.onload = async function() {
|
| 918 |
try {
|
|
|
|
| 926 |
}
|
| 927 |
S.pending = { type: 'text', data: fullText.trim() || "No text found in PDF.", name: file.name };
|
| 928 |
showToast("PDF converted to text successfully!");
|
| 929 |
+
} catch (e) {
|
| 930 |
+
showToast("Error parsing PDF."); removeImg();
|
| 931 |
+
}
|
| 932 |
};
|
| 933 |
reader.readAsArrayBuffer(file);
|
| 934 |
}
|
| 935 |
+
// B. Word (.docx) Extraction
|
| 936 |
+
else if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) {
|
| 937 |
reader.onload = async function() {
|
| 938 |
try {
|
| 939 |
const arrayBuffer = this.result;
|
| 940 |
const result = await mammoth.extractRawText({arrayBuffer: arrayBuffer});
|
| 941 |
S.pending = { type: 'text', data: result.value.trim() || "No text found in DOCX.", name: file.name };
|
| 942 |
showToast("Word document converted to text!");
|
| 943 |
+
} catch(e) {
|
| 944 |
+
showToast("Error parsing Word doc."); removeImg();
|
| 945 |
+
}
|
| 946 |
};
|
| 947 |
reader.readAsArrayBuffer(file);
|
| 948 |
}
|
| 949 |
+
// C. HTML, Code, Text Extraction (Direct Read)
|
| 950 |
else {
|
| 951 |
reader.onload = e => {
|
| 952 |
S.pending = { type: 'text', data: e.target.result, name: file.name };
|
| 953 |
+
showToast("Code/Text file attached and extracted!");
|
| 954 |
};
|
| 955 |
reader.readAsText(file);
|
| 956 |
}
|
| 957 |
}
|
| 958 |
+
|
| 959 |
+
// Reset file input so same file can be selected again
|
| 960 |
+
input.value = '';
|
| 961 |
}
|
| 962 |
|
| 963 |
function removeImg(){ S.pending=null; document.getElementById('imgPrev').classList.remove('show'); }
|
| 964 |
|
| 965 |
+
/* ==========================================================================
|
| 966 |
+
UI UTILITIES (เคธเฅเคตเคฟเคงเคพเคเคจเค เคซเคเคเฅเคถเคจเฅเคธ)
|
| 967 |
+
========================================================================== */
|
| 968 |
+
function handleKey(e){ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); sendMsg(); } }
|
| 969 |
+
function autoGrow(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,200)+'px'; }
|
| 970 |
+
|
| 971 |
+
function checkScroll() {
|
| 972 |
+
const m = document.getElementById('msgs');
|
| 973 |
+
const isAtBottom = Math.abs((m.scrollHeight - m.scrollTop) - m.clientHeight) < 80;
|
| 974 |
+
S.isUserScrolling = !isAtBottom;
|
| 975 |
+
const btn = document.getElementById('scrollBtn');
|
| 976 |
+
if(S.isUserScrolling) btn.classList.add('show'); else btn.classList.remove('show');
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
function forceScrollDown(){
|
| 980 |
+
const m = document.getElementById('msgs');
|
| 981 |
+
m.scrollTo({top: m.scrollHeight, behavior: 'smooth'});
|
| 982 |
+
S.isUserScrolling = false;
|
| 983 |
+
document.getElementById('scrollBtn').classList.remove('show');
|
| 984 |
+
}
|
| 985 |
+
|
| 986 |
+
function autoScroll() {
|
| 987 |
+
if(!S.isUserScrolling) {
|
| 988 |
+
const m = document.getElementById('msgs');
|
| 989 |
+
m.scrollTop = m.scrollHeight;
|
| 990 |
+
}
|
| 991 |
+
}
|
| 992 |
+
|
| 993 |
+
/* ==========================================================================
|
| 994 |
+
WORKSPACE RENDERING & STREAMING UI (เคเฅเคก เคตเคฐเฅเคเคธเฅเคชเฅเคธ เคฐเฅเคเคกเคฐเคฐ)
|
| 995 |
+
========================================================================== */
|
| 996 |
+
function openWorkspace(id) {
|
| 997 |
+
S.currentWorkspaceId = id;
|
| 998 |
+
const block = S.codeBlocks[id];
|
| 999 |
+
document.getElementById('wsTitleText').textContent = block.lang ? `Generated ${block.lang.toUpperCase()}` : "Generated Code";
|
| 1000 |
+
const codeBlock = document.getElementById('wsCodeBlock');
|
| 1001 |
+
codeBlock.className = `hljs language-${block.lang || 'plaintext'}`;
|
| 1002 |
+
codeBlock.textContent = block.code;
|
| 1003 |
+
hljs.highlightElement(codeBlock);
|
| 1004 |
+
document.getElementById('workspace').classList.add('open');
|
| 1005 |
+
if(window.innerWidth <= 850) {
|
| 1006 |
+
document.getElementById('sidebar').classList.remove('show');
|
| 1007 |
+
}
|
| 1008 |
+
}
|
| 1009 |
+
|
| 1010 |
function closeWorkspace() { document.getElementById('workspace').classList.remove('open'); }
|
| 1011 |
+
|
| 1012 |
+
// MARKED.JS Custom Renderer (handles 'Generating Code' UI logic)
|
| 1013 |
const renderer = new marked.Renderer();
|
| 1014 |
renderer.code = function(token) {
|
| 1015 |
+
const codeText = typeof token === 'string' ? token : (token.text || '');
|
| 1016 |
+
const language = typeof token === 'string' ? arguments[1] : (token.lang || '');
|
| 1017 |
+
|
| 1018 |
+
// NEW FEATURE: Gemini style "Generating Code..." UI while streaming
|
| 1019 |
+
if (window.isStreamingGlobalFlag) {
|
| 1020 |
+
return `
|
| 1021 |
+
<div class="streaming-code-wrapper">
|
| 1022 |
+
<div class="streaming-code-header">
|
| 1023 |
+
<div class="loader-spinner"></div>
|
| 1024 |
+
Generating Code...
|
| 1025 |
+
</div>
|
| 1026 |
+
<pre><code class="language-${language}">${codeText.replace(/</g, '<').replace(/>/g, '>')}</code></pre>
|
| 1027 |
+
</div>`;
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
// When streaming stops, generate the Workspace View Button Card
|
| 1031 |
+
const id = S.codeBlocks.length;
|
| 1032 |
+
S.codeBlocks.push({ code: codeText, lang: language });
|
| 1033 |
+
return `
|
| 1034 |
+
<div class="ws-trigger-card">
|
| 1035 |
+
<div class="ws-trigger-info">
|
| 1036 |
+
<div class="ws-trigger-icon">๐ป</div>
|
| 1037 |
+
<div class="ws-trigger-text">
|
| 1038 |
+
<h4>Code Generated</h4>
|
| 1039 |
+
<p>${language || 'Snippet'}</p>
|
| 1040 |
+
</div>
|
| 1041 |
+
</div>
|
| 1042 |
+
<button class="ws-btn" onclick="openWorkspace(${id})">View in Workspace</button>
|
| 1043 |
+
</div>`;
|
| 1044 |
};
|
| 1045 |
marked.use({ renderer });
|
| 1046 |
|
|
|
|
| 1051 |
return { mainText: text.trim(), thinkText: thinkContent.trim() };
|
| 1052 |
}
|
| 1053 |
|
| 1054 |
+
/* ==========================================================================
|
| 1055 |
+
MAIN CHAT CONTROLLER (เคฎเฅเคเฅเคฏ เคเฅเค เคฒเฅเคเคฟเค)
|
| 1056 |
+
========================================================================== */
|
| 1057 |
async function sendMsg(){
|
| 1058 |
+
if(S.busy)return;
|
| 1059 |
+
const inp = document.getElementById('chatInput');
|
| 1060 |
+
const msg = inp.value.trim();
|
| 1061 |
+
if(!msg && !S.pending) return;
|
| 1062 |
+
|
| 1063 |
+
// Check Guest Limit
|
| 1064 |
+
if (!currentUser) {
|
| 1065 |
+
if (guestCount >= 3) {
|
| 1066 |
+
openAuthModal();
|
| 1067 |
+
return showToast("Guest limit reached. Please Login.");
|
| 1068 |
+
}
|
| 1069 |
+
guestCount++;
|
| 1070 |
+
localStorage.setItem('arjun_guest', guestCount.toString());
|
| 1071 |
+
document.getElementById('guestCountDisplay').innerText = guestCount;
|
| 1072 |
+
}
|
| 1073 |
+
|
| 1074 |
+
inp.value=''; inp.style.height='auto';
|
| 1075 |
+
document.getElementById('sendBtn').disabled=true;
|
| 1076 |
+
S.busy=true;
|
| 1077 |
|
| 1078 |
+
const w = document.getElementById('welcome');
|
| 1079 |
+
if(w) w.style.display='none';
|
| 1080 |
+
|
| 1081 |
+
let backendPayloadMsg = msg;
|
| 1082 |
+
let attachments = [];
|
| 1083 |
+
let badge = null;
|
| 1084 |
+
|
| 1085 |
+
// Multi-modal attachment routing
|
| 1086 |
+
if(S.pending) {
|
| 1087 |
+
if(S.pending.type === 'text') {
|
| 1088 |
+
// PDF/DOC/Code extracted text
|
| 1089 |
+
backendPayloadMsg = `[Attached File Data: ${S.pending.name}]\n\n---FILE CONTENT START---\n${S.pending.data}\n---FILE CONTENT END---\n\nUser Question: ${msg}`;
|
| 1090 |
+
badge = '๐ '+S.pending.name;
|
| 1091 |
+
} else {
|
| 1092 |
+
// Raw Image/Audio/Video processing
|
| 1093 |
+
attachments.push({ type: S.pending.type, data: S.pending.data, name: S.pending.name });
|
| 1094 |
+
const icon = S.pending.type === 'video' ? '๐ฅ ' : (S.pending.type === 'audio' ? '๐ต ' : '๐ผ๏ธ ');
|
| 1095 |
+
badge = icon + S.pending.name;
|
| 1096 |
+
}
|
| 1097 |
+
appendUserDOM(msg || '[File Attached]', badge);
|
| 1098 |
+
S.history.push({role: 'user', content: backendPayloadMsg, badge: badge});
|
| 1099 |
+
} else {
|
| 1100 |
+
appendUserDOM(msg);
|
| 1101 |
+
S.history.push({role: 'user', content: msg});
|
| 1102 |
+
}
|
| 1103 |
|
| 1104 |
+
removeImg();
|
| 1105 |
+
syncHistory();
|
| 1106 |
+
|
| 1107 |
+
const botDiv = document.createElement('div');botDiv.className='msg bot';
|
| 1108 |
+
botDiv.innerHTML=`<div class="avatar"><img src="${LOGO_URL}"></div><div class="body"><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div></div>`;
|
| 1109 |
+
document.getElementById('msgs').appendChild(botDiv);
|
| 1110 |
+
forceScrollDown();
|
| 1111 |
+
|
| 1112 |
+
// STRICT SYSTEM PROMPT (Bilingual & Pollinations support)
|
| 1113 |
+
const sysPrompt = `You are Arjun 2.O, a state-of-the-art AI assistant developed by Abhay Kumar.
|
| 1114 |
+
LANGUAGE PROTOCOL: You are fully bilingual. You MUST write and respond fluently in BOTH English and Hindi.
|
| 1115 |
+
1. If the user asks in English, reply entirely in English.
|
| 1116 |
+
2. If the user asks in Hindi, reply in Hindi.
|
| 1117 |
+
3. If the user uses a mix (Hinglish), you can respond naturally in the same mixed tone.
|
| 1118 |
+
NEVER force Hindi when the user is speaking English. Always adapt dynamically to the user's language.
|
| 1119 |
|
| 1120 |
+
CRITICAL IMAGE GENERATION PROTOCOL:
|
| 1121 |
+
If the user asks to generate, create, or draw an image/photo, you MUST generate the image by responding with a markdown image tag using Pollinations AI.
|
| 1122 |
+
Format to use: 
|
| 1123 |
+
Available models for {MODEL}: flux, flux-realism, flux-anime, flux-3d.
|
| 1124 |
+
Choose the best model based on the prompt. Example: if user says anime, use flux-anime.
|
| 1125 |
+
Always include this markdown tag when image generation is requested.`;
|
| 1126 |
|
| 1127 |
+
// Stripping extra 'badge' property before sending to backend to avoid API errors
|
| 1128 |
+
const cleanHistory = S.history.slice(0, -1).map(item => ({ role: item.role, content: item.content }));
|
| 1129 |
+
|
| 1130 |
+
const payload = {
|
| 1131 |
+
message: backendPayloadMsg,
|
| 1132 |
+
attachments: attachments,
|
| 1133 |
+
system_prompt: sysPrompt,
|
| 1134 |
+
history: cleanHistory,
|
| 1135 |
+
max_tokens: 4096,
|
| 1136 |
+
temperature: 0.7
|
| 1137 |
+
};
|
| 1138 |
+
|
| 1139 |
+
try {
|
| 1140 |
+
const res = await fetch('/api/chat', {
|
| 1141 |
+
method: 'POST',
|
| 1142 |
+
headers: {'Content-Type': 'application/json'},
|
| 1143 |
+
body: JSON.stringify(payload)
|
| 1144 |
+
});
|
| 1145 |
+
|
| 1146 |
+
if (!res.ok) throw new Error("Server error");
|
| 1147 |
+
|
| 1148 |
+
const reader = res.body.getReader();
|
| 1149 |
+
const decoder = new TextDecoder();
|
| 1150 |
+
let accumulatedText = "";
|
| 1151 |
+
|
| 1152 |
+
window.isStreamingGlobalFlag = true;
|
| 1153 |
+
|
| 1154 |
+
while (true) {
|
| 1155 |
+
const { done, value } = await reader.read();
|
| 1156 |
+
if (done) break;
|
| 1157 |
+
const chunk = decoder.decode(value, {stream: true});
|
| 1158 |
+
const lines = chunk.split('\n');
|
| 1159 |
+
|
| 1160 |
+
for (let line of lines) {
|
| 1161 |
+
if (line.startsWith('data: ')) {
|
| 1162 |
+
const dataStr = line.substring(6);
|
| 1163 |
+
if (dataStr.trim() === '[DONE]') continue;
|
| 1164 |
+
try {
|
| 1165 |
+
const dataObj = JSON.parse(dataStr);
|
| 1166 |
+
if (dataObj.choices && dataObj.choices[0].delta.content) {
|
| 1167 |
+
accumulatedText += dataObj.choices[0].delta.content;
|
| 1168 |
+
// Stream with updating UI (triggers Generating Code visual)
|
| 1169 |
+
renderBotText(botDiv, accumulatedText, true);
|
| 1170 |
+
autoScroll();
|
| 1171 |
+
}
|
| 1172 |
+
} catch (e) { /* silent catch for partial chunks */ }
|
| 1173 |
+
}
|
| 1174 |
}
|
| 1175 |
}
|
| 1176 |
+
|
| 1177 |
+
window.isStreamingGlobalFlag = false;
|
| 1178 |
+
|
| 1179 |
+
// Final render (Transforms "Generating Code" into "Workspace Buttons")
|
| 1180 |
+
renderBotText(botDiv, accumulatedText, false);
|
| 1181 |
+
S.history.push({role: 'bot', content: accumulatedText});
|
| 1182 |
+
syncHistory();
|
| 1183 |
+
|
| 1184 |
+
} catch(err) {
|
| 1185 |
+
window.isStreamingGlobalFlag = false;
|
| 1186 |
+
renderBotText(botDiv, `**Error:** Server communication failed.`, false);
|
| 1187 |
}
|
| 1188 |
+
|
| 1189 |
+
S.busy = false;
|
| 1190 |
+
document.getElementById('sendBtn').disabled = false;
|
| 1191 |
+
autoScroll();
|
|
|
|
|
|
|
| 1192 |
}
|
| 1193 |
|
| 1194 |
+
function sendEx(t){ document.getElementById('chatInput').value=t; sendMsg(); }
|
| 1195 |
+
|
| 1196 |
+
function appendUserDOM(text, badgeText){
|
| 1197 |
+
let bHtml = badgeText ? `<div style="font-size:11px;color:var(--accent);margin-bottom:4px;font-weight:600;">${esc(badgeText)}</div>` : '';
|
| 1198 |
+
const el = document.createElement('div');
|
| 1199 |
+
el.className = 'msg user';
|
| 1200 |
+
el.innerHTML = `<div class="avatar">U</div><div class="body">${bHtml}<div class="bubble">${esc(text)}</div></div>`;
|
| 1201 |
+
document.getElementById('msgs').appendChild(el);
|
| 1202 |
+
}
|
| 1203 |
+
|
| 1204 |
+
function appendBotDOM(content, isHistoryLoading=false) {
|
| 1205 |
+
const el = document.createElement('div');
|
| 1206 |
+
el.className = 'msg bot';
|
| 1207 |
+
el.innerHTML = `<div class="avatar"><img src="${LOGO_URL}"></div><div class="body"><div class="bubble"></div></div>`;
|
| 1208 |
+
document.getElementById('msgs').appendChild(el);
|
| 1209 |
+
renderBotText(el, content, !isHistoryLoading);
|
| 1210 |
+
}
|
| 1211 |
+
|
| 1212 |
+
// Bot DOM Renderer (Text, Markdown, Actions & TTS)
|
| 1213 |
+
function renderBotText(el, raw, isStreaming) {
|
| 1214 |
+
const b = el.querySelector('.bubble');
|
| 1215 |
+
const { mainText, thinkText } = parseThinking(raw);
|
| 1216 |
+
let html = '';
|
| 1217 |
+
|
| 1218 |
+
if (thinkText) {
|
| 1219 |
+
html += `<div class="think-box"><div class="think-title">๐ง Reasoning Process</div>${esc(thinkText)}</div>`;
|
| 1220 |
+
}
|
| 1221 |
+
|
| 1222 |
+
html += marked.parse(mainText);
|
| 1223 |
+
|
| 1224 |
+
if(isStreaming) {
|
| 1225 |
+
html += '<span class="blinking-cursor"></span>';
|
| 1226 |
+
}
|
| 1227 |
+
|
| 1228 |
+
b.innerHTML = html;
|
| 1229 |
+
|
| 1230 |
+
let actionsDiv = el.querySelector('.msg-actions');
|
| 1231 |
+
|
| 1232 |
+
// Add action buttons only when streaming is completely done
|
| 1233 |
+
if(!isStreaming && !actionsDiv && mainText.trim().length > 0) {
|
| 1234 |
+
// Ensure string is properly escaped for passing into onclick function
|
| 1235 |
+
const cleanRaw = mainText.replace(/"/g, '"').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
|
| 1236 |
+
const rawForTTS = mainText.replace(/"/g, '"').replace(/'/g, "\\'").replace(/\n/g, ' ');
|
| 1237 |
+
|
| 1238 |
+
const actionsHTML = `
|
| 1239 |
+
<div class="msg-actions">
|
| 1240 |
+
<!-- ROBUST COPY BUTTON -->
|
| 1241 |
+
<button class="action-btn" onclick="copyText(this, \`${cleanRaw}\`)">
|
| 1242 |
+
<svg width="14" height="14" 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>
|
| 1243 |
+
Copy Text
|
| 1244 |
+
</button>
|
| 1245 |
+
<!-- TEXT TO SPEECH (PRABHAT NEURAL) BUTTON -->
|
| 1246 |
+
<button class="action-btn tts-btn" onclick="speakText(\`${rawForTTS}\`)">
|
| 1247 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>
|
| 1248 |
+
Read Aloud
|
| 1249 |
+
</button>
|
| 1250 |
+
</div>`;
|
| 1251 |
+
|
| 1252 |
+
el.querySelector('.body').insertAdjacentHTML('beforeend', actionsHTML);
|
| 1253 |
+
}
|
| 1254 |
+
}
|
| 1255 |
+
|
| 1256 |
+
function esc(t){ return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
| 1257 |
+
|
| 1258 |
</script>
|
| 1259 |
</body>
|
| 1260 |
</html>
|