CodeVed / index.html
Vedika
Update index.html
57be3ea verified
Raw
History Blame
70 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Arjun 2.O - Advanced AI</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<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">
<!-- Highlight.js for Syntax Highlighting -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<!-- PDF.js for PDF to Text Extraction -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<!-- Mammoth.js for Word (.docx) to Text Extraction -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<!-- Highlight.js and Marked.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
<style>
/* ==========================================================================
BASE RESET & VARIABLES (आधारभूत सेटिंग्स)
========================================================================== */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-color: #ffffff;
--bg-secondary: #f9f9fb;
--text-main: #1d1d1f;
--text-muted: #86868b;
--accent: #FF9933;
--accent-green: #138808;
--accent-hover: #e68a2e;
--border-light: rgba(0, 0, 0, 0.08);
--border-focus: rgba(255, 153, 51, 0.3);
--glass-bg: rgba(255, 255, 255, 0.85);
--glass-border: rgba(255, 255, 255, 0.4);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.04);
--shadow-md: 0 8px 32px rgba(0,0,0,0.08);
--shadow-lg: 0 16px 48px rgba(0,0,0,0.12);
--font-serif: 'Instrument Serif', serif;
--font-sans: 'Inter', -apple-system, sans-serif;
--font-mono: 'Geist Mono', monospace;
--radius-lg: 24px;
--radius-md: 16px;
--radius-sm: 8px;
}
html, body {
height: 100dvh;
overflow: hidden;
background: var(--bg-color);
color: var(--text-main);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
}
/* ==========================================================================
BACKGROUND ANIMATIONS (बैकग्राउंड इफ़ेक्ट)
========================================================================== */
.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%); }
.orb { position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.5; animation: drift 25s ease-in-out infinite alternate; }
.orb1 { width: 600px; height: 600px; background: radial-gradient(circle, rgba(255,153,51,0.2), transparent 70%); top: -150px; left: -100px; }
.orb2 { width: 500px; height: 500px; background: radial-gradient(circle, rgba(19,136,8,0.18), transparent 70%); bottom: -100px; right: -100px; animation-delay: -10s; }
@keyframes drift { 0% { transform: translate(0,0) scale(1); } 100% { transform: translate(60px,40px) scale(1.05); } }
/* ==========================================================================
TOAST NOTIFICATION SYSTEM (नोटिफिकेशन)
========================================================================== */
#toast-container { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); z-index: 10000; display: flex; flex-direction: column; gap: 10px; }
.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; }
@keyframes slideDownFade { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
/* ==========================================================================
WELCOME OVERLAY (प्रारंभिक स्क्रीन)
========================================================================== */
.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; }
.welcome-overlay.hidden { opacity: 0; pointer-events: none; }
.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); }
.welcome-overlay h1 { font-family: var(--font-sans); font-size: 36px; font-weight: 800; color: var(--text-main); letter-spacing: -1px; margin-bottom: 12px; }
.welcome-overlay p { font-size: 16px; color: var(--text-muted); line-height: 1.6; max-width: 450px; margin-bottom: 36px; }
.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); }
.start-btn:hover { background: #333; transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0,0,0,0.2); }
/* ==========================================================================
LAYOUT STRUCTURING (मुख्य ढांचा)
========================================================================== */
.shell { position: relative; z-index: 1; display: flex; height: 100dvh; width: 100vw; overflow: hidden; }
.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; }
.main-wrapper { flex: 1; display: flex; position: relative; overflow: hidden; }
.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%; }
.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; }
.workspace.open { width: 50%; }
/* ==========================================================================
SIDEBAR COMPONENTS (साइडबार तत्व)
========================================================================== */
.logo-area { padding: 24px 20px; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; }
.logo-row { display: flex; align-items: center; gap: 12px; }
.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; }
.logo-text { line-height: 1.2; }
.logo-name { font-family: var(--font-sans); font-weight: 800; font-size: 16px; color: var(--text-main); letter-spacing: -0.5px; }
.logo-sub { font-size: 10px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); }
.close-sidebar-btn { display: none; background: none; border: none; font-size: 20px; color: var(--text-main); cursor: pointer; }
/* Auth Block */
.auth-block { padding: 20px; border-bottom: 1px solid var(--border-light); }
.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); }
.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; }
.user-email { flex: 1; font-size: 12px; font-weight: 600; color: var(--text-main); text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
.logout-btn { background: none; border: none; color: #ff3b30; cursor: pointer; font-size: 16px; padding: 4px; }
.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; }
.login-prompt-btn:hover { background: #333; transform: translateY(-1px); box-shadow: var(--shadow-sm); }
/* Settings / Clear Button */
.settings { padding: 20px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 20px; }
.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; }
.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); }
.brand-box h3 { font-family: var(--font-sans); font-size: 15px; font-weight: 700; color: var(--text-main); margin-bottom: 6px; }
.brand-box p { font-size: 10px; font-weight: 600; color: var(--accent-green); line-height: 1.4; letter-spacing: 0.5px; }
.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; }
.clear-btn:hover { background: #fff0f0; color: #ff3b30; border-color: #ff3b30; }
/* ==========================================================================
MAIN CHAT AREA (मुख्य चैट क्षेत्र)
========================================================================== */
.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; }
.mobile-menu-btn { display: none; background: none; border: none; font-size: 24px; color: var(--text-main); cursor: pointer; margin-right: 16px; }
.model-badge { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #34c759; box-shadow: 0 0 0 2px rgba(52,199,89,0.2); }
.messages { flex: 1; overflow-y: auto; padding: 30px 40px; display: flex; flex-direction: column; gap: 24px; scroll-behavior: smooth; position: relative; min-height: 0; }
.messages::-webkit-scrollbar { width: 6px; }
.messages::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 10px; }
/* Inner Welcome Screen */
.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; }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
.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); }
.welcome-title { font-size: 32px; font-weight: 800; letter-spacing: -1px; margin-bottom: 12px; }
.welcome-sub { font-size: 15px; color: var(--text-muted); line-height: 1.6; margin-bottom: 20px; max-width: 500px; }
.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; }
.ex-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; width: 100%; }
.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); }
.ex-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--accent); }
.ex-title { font-size: 13px; font-weight: 600; color: var(--text-main); margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
.ex-desc { font-size: 12px; color: var(--text-muted); line-height: 1.4; }
/* Message Bubbles */
.msg { display: flex; gap: 16px; animation: msgIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; max-width: 85%; }
@keyframes msgIn { from { opacity: 0; transform: translateY(15px) scale(0.96); } to { opacity: 1; transform: none; } }
.msg.user { flex-direction: row-reverse; align-self: flex-end; }
.msg.bot { align-self: flex-start; }
.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; }
.msg.user .avatar { background: #f0f0f0; color: var(--text-main); border: 1px solid var(--border-light); }
.msg.bot .avatar { border: 1px solid var(--border-light); background: #fff; padding: 2px; }
.msg.bot .avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.body { display: flex; flex-direction: column; gap: 6px; max-width: 100%; width: 100%; }
.msg.user .body { align-items: flex-end; }
.bubble { padding: 16px 20px; font-size: 15px; line-height: 1.6; color: var(--text-main); position: relative; word-break: break-word; overflow-wrap: anywhere; }
.msg.user .bubble { background: var(--text-main); color: #ffffff; border-radius: 20px 20px 4px 20px; box-shadow: var(--shadow-sm); }
.msg.bot .bubble { background: #ffffff; border: 1px solid var(--border-light); border-radius: 4px 20px 20px 20px; box-shadow: var(--shadow-sm); width: 100%; }
/* Bot Actions (Copy & Text to Speech) */
.msg-actions { display: flex; gap: 8px; margin-top: 4px; padding-left: 12px; opacity: 0.7; transition: opacity 0.2s; flex-wrap: wrap;}
.msg.bot:hover .msg-actions { opacity: 1; }
.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; }
.action-btn:hover { background: #e5e5ea; transform: translateY(-1px); }
.action-btn.tts-btn { color: var(--accent-green); }
/* Reasoning Box (Hidden as requested) */
.think-box { display: none; }
/* Markdown Formatting */
.bubble p { margin-bottom: 12px; }
.bubble p:last-child { margin-bottom: 0; }
.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; }
.msg.user .bubble code { background: rgba(255,255,255,0.2); color: #fff; }
.bubble img { max-width: 100%; border-radius: 12px; margin: 10px 0; box-shadow: var(--shadow-sm); border: 1px solid var(--border-light); }
.bubble pre { background: #1d1d1f; color: #fff; padding: 16px; border-radius: 12px; overflow-x: auto; margin: 10px 0;}
.bubble pre code { background: transparent; color: #fff; padding: 0;}
/* ==========================================================================
CODE GENERATING UI (नया फीचर: "Generating Code...")
========================================================================== */
.streaming-code-wrapper { background: #1d1d1f; border-radius: 12px; border: 1px solid #333; overflow: hidden; margin: 10px 0; box-shadow: var(--shadow-md); }
.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); }
.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; }
@keyframes spin { to { transform: rotate(360deg); } }
.streaming-code-wrapper pre { margin: 0; background: transparent; border-radius: 0; }
/* Final Workspace Trigger Card */
.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); }
.ws-trigger-info { display: flex; align-items: center; gap: 12px; }
.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); }
.ws-trigger-text h4 { font-size: 14px; font-weight: 600; margin-bottom: 2px; }
.ws-trigger-text p { font-size: 12px; color: var(--text-muted); font-family: var(--font-mono); text-transform: uppercase; }
.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; }
.ws-btn:hover { transform: scale(1.05); background: var(--accent); }
/* Typing & Cursor */
.blinking-cursor { display: inline-block; width: 8px; height: 16px; background: var(--accent); margin-left: 4px; animation: blink 1s step-end infinite; vertical-align: middle; }
@keyframes blink { 50% { opacity: 0; } }
.typing { display: flex; align-items: center; gap: 6px; padding: 4px; }
.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; }
.typing span:nth-child(1) { animation-delay: -0.32s; }
.typing span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
/* Scroll to Bottom */
.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; }
.scroll-bottom-btn.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
.scroll-bottom-btn:hover { background: #fff; transform: translateX(-50%) translateY(-3px); box-shadow: var(--shadow-lg); }
/* ==========================================================================
INPUT AREA & ATTACHMENT MENU (इनपुट और फाइल अटैचमेंट)
========================================================================== */
.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; }
.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; }
.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); }
.img-prev-inline { display: none; padding: 12px 20px 0; align-items: center; gap: 12px; }
.img-prev-inline.show { display: flex; animation: fadeIn 0.3s; }
.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; }
.inline-info { flex: 1; }
.inline-nm { font-size: 12px; font-weight: 600; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.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; }
.inline-rm:hover { background: #ff3b30; color: #fff; }
.input-row { display: flex; align-items: flex-end; gap: 12px; padding: 12px 16px 12px 20px; }
.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; }
.chat-ta::placeholder { color: var(--text-muted); }
/* Gemini Style Attachment Menu */
.attach-wrapper { position: relative; }
.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; }
.attach-btn:hover { background: rgba(0,0,0,0.04); color: var(--text-main); }
.attach-btn svg { width: 20px; height: 20px; }
.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); }
.attach-menu.show { display: flex; transform: translateY(0); opacity: 1; }
.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; }
.attach-menu-item:hover { background: rgba(0,0,0,0.05); }
.attach-menu-item span.icon { font-size: 16px; }
/* Hidden Inputs */
.hidden-file-input { display: none; }
.voice-btn.recording { color: #ff3b30; animation: pulseRecord 1.5s infinite; }
@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); } }
.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); }
.send-btn svg { width: 18px; height: 18px; fill: #1d1d1f; transition: transform 0.2s; }
.send-btn:hover { box-shadow: 0 6px 16px rgba(255,153,51,0.3); transform: translate(2px, -2px); }
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(100%); transform: none; }
.input-hint { text-align: center; font-size: 11px; color: var(--text-muted); margin-top: 12px; font-weight: 500; }
/* ==========================================================================
WORKSPACE PANEL (कोड वर्कस्पेस)
========================================================================== */
.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; }
.ws-title { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.ws-actions { display: flex; gap: 8px; }
.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; }
.ws-icon-btn:hover { background: #e5e5ea; }
.ws-close { padding: 6px; background: transparent; border-color: transparent; }
.ws-close:hover { color: #ff3b30; background: #fff0f0; }
.ws-content { flex: 1; overflow-y: auto; background: #fafafa; padding: 20px; }
/* Modified workspace pre styling to match Highlight.js dark theme better if needed,
but since background is #fafafa, we need a dark box for code */
.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;}
/* ==========================================================================
AUTH MODAL (लॉगिन सिस्टम)
========================================================================== */
.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; }
.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; }
.close-modal { position: absolute; top: 20px; right: 20px; background: none; border: none; font-size: 24px; color: var(--text-muted); cursor: pointer; transition: 0.2s; }
.close-modal:hover { color: var(--text-main); transform: rotate(90deg); }
.auth-header { text-align: center; margin-bottom: 24px; }
.auth-header h2 { font-family: var(--font-sans); font-size: 24px; font-weight: 800; color: var(--text-main); }
.auth-tabs { display: flex; background: var(--bg-secondary); padding: 4px; border-radius: 12px; margin-bottom: 24px; }
.auth-tab { flex: 1; padding: 10px; text-align: center; border-radius: 8px; font-size: 14px; font-weight: 700; color: var(--text-muted); cursor: pointer; transition: 0.3s; }
.auth-tab.active { background: #fff; color: var(--text-main); box-shadow: var(--shadow-sm); }
.auth-step { display: none; flex-direction: column; animation: slideIn 0.3s ease; }
.auth-step.active { display: flex; }
.input-group { margin-bottom: 16px; }
.modal-card input { width: 100%; padding: 14px 16px; border: 1px solid var(--border-light); border-radius: 12px; font-size: 15px; outline: none; background: #fafafa; transition: 0.3s; font-family: var(--font-sans); }
.modal-card input:focus { border-color: var(--text-main); background: #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.primary-btn { width: 100%; padding: 14px; background: var(--text-main); color: #fff; border: none; border-radius: 12px; font-size: 15px; font-weight: 600; cursor: pointer; transition: 0.2s; margin-top: 8px; }
.primary-btn:hover { background: #333; transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.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; }
.secondary-btn:hover { color: var(--text-main); }
/* ==========================================================================
MOBILE RESPONSIVENESS (मोबाइल लेआउट)
========================================================================== */
@media(max-width:850px){
.sidebar { position: absolute; left: -280px; z-index: 100; height: 100%; transition: left 0.3s ease; }
.sidebar.show { left: 0; box-shadow: var(--shadow-lg); }
.mobile-menu-btn { display: block; }
.close-sidebar-btn { display: block; }
.workspace.open { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; }
.messages { padding: 20px; }
.input-container { padding: 10px 15px 15px; }
.msg { max-width: 95%; }
.scroll-bottom-btn { bottom: 110px; }
}
</style>
</head>
<body>
<!-- TOAST NOTIFICATION -->
<div id="toast-container"></div>
<!-- GET STARTED WELCOME OVERLAY -->
<div class="welcome-overlay" id="welcomeOverlay">
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo">
<h1>Welcome to Arjun 2.O</h1>
<p>Developed by Abhay Kumar. Experience advanced reasoning, secure workspace coding, and high-quality image generation in one place.</p>
<button class="start-btn" onclick="startArjunApp()">Get Started</button>
</div>
<!-- BACKGROUND ANIMATION -->
<div class="bg"><div class="orb orb1"></div><div class="orb orb2"></div></div>
<div class="shell">
<!-- SIDEBAR -->
<aside class="sidebar" id="sidebar">
<div class="logo-area">
<div class="logo-row">
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo" class="logo-img">
<div class="logo-text">
<div class="logo-name">ARJUN 2.O</div>
<div class="logo-sub">Advanced Edition</div>
</div>
</div>
<button class="close-sidebar-btn" onclick="toggleSidebar()"></button>
</div>
<div class="auth-block">
<button id="loginPromptBtn" class="login-prompt-btn" onclick="openAuthModal()">🔒 Secure Login</button>
<div id="userProfile" class="user-profile">
<div class="user-avatar" id="uAvatar">U</div>
<div class="user-email" id="uEmail">user@email.com</div>
<button class="logout-btn" onclick="logout()" title="Logout">🚪</button>
</div>
</div>
<div class="settings">
<div class="brand-box">
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Identity">
<h3>Arjun 2.O</h3>
<p>DEVELOPED BY ABHAY KUMAR</p>
</div>
<button class="clear-btn" onclick="clearMemory()">🗑️ Clear Chat Memory</button>
</div>
</aside>
<!-- MAIN CHAT AREA -->
<div class="main-wrapper">
<main class="chat-main" id="chatMain">
<header class="chat-hdr">
<button class="mobile-menu-btn" onclick="toggleSidebar()"></button>
<div class="model-badge">
<div class="status-dot"></div>
Arjun Intelligence
</div>
<div style="width:24px;"></div>
</header>
<div class="messages" id="msgs" onscroll="checkScroll()">
<!-- Welcome Screen inside Chat -->
<div class="welcome" id="welcome">
<img src="https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png" alt="Arjun Logo" class="welcome-logo">
<div class="welcome-title">Welcome, User!</div>
<div class="welcome-sub">I am Arjun 2.O, developed by Abhay Kumar. I am equipped with advanced intelligence, workspace coding, and image generation.</div>
<div id="guestBadge" class="guest-badge">Guest Mode: <span id="guestCountDisplay">0</span>/3 Queries Used.</div>
<div class="ex-grid">
<div class="ex-card" onclick="sendEx('Write a python script for a simple HTTP server. Provide the code.')">
<div class="ex-title">💻 Code Generation</div>
<div class="ex-desc">Generate secure, workspace-ready code</div>
</div>
<div class="ex-card" onclick="sendEx('Create an image of a futuristic Indian city with flying cars, highly detailed, 8k resolution.')">
<div class="ex-title">🎨 Generate Images</div>
<div class="ex-desc">Stunning visuals using Pollinations AI</div>
</div>
</div>
</div>
</div>
<button class="scroll-bottom-btn" id="scrollBtn" onclick="forceScrollDown()">
<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>
</button>
<!-- INPUT CONTAINER -->
<div class="input-container">
<div class="input-wrap">
<div class="img-prev-inline" id="imgPrev">
<img class="inline-th" id="imgThumb" src="" style="display:none;">
<div class="inline-th" id="fileIcon" style="display:none;">📄</div>
<div class="inline-info">
<div class="inline-nm" id="imgName">file.txt</div>
</div>
<button class="inline-rm" onclick="removeImg()"></button>
</div>
<div class="input-row">
<!-- GEMINI STYLE ATTACHMENT MENU -->
<div class="attach-wrapper">
<button class="attach-btn" id="attachToggleBtn" onclick="toggleAttachMenu()" title="Attach File">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
</button>
<div class="attach-menu" id="attachMenu">
<div class="attach-menu-item" onclick="triggerInput('image')"><span class="icon">🖼️</span> Image</div>
<div class="attach-menu-item" onclick="triggerInput('video')"><span class="icon">🎥</span> Video</div>
<div class="attach-menu-item" onclick="triggerInput('audio')"><span class="icon">🎵</span> Audio</div>
<div class="attach-menu-item" onclick="triggerInput('document')"><span class="icon">📄</span> Document / Code</div>
</div>
<!-- Hidden Inputs for strict validation -->
<input type="file" id="inp-image" class="hidden-file-input" accept="image/*" onchange="handleSpecificFile(this, 'image')">
<input type="file" id="inp-video" class="hidden-file-input" accept="video/*" onchange="handleSpecificFile(this, 'video')">
<input type="file" id="inp-audio" class="hidden-file-input" accept="audio/*" onchange="handleSpecificFile(this, 'audio')">
<!-- Accept all major document and code extensions -->
<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')">
</div>
<!-- Voice Input Mic (Smart Punctuation & Hindi/English) -->
<button class="attach-btn voice-btn" id="voiceBtn" onclick="toggleVoice()" title="Voice Input">
<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>
</button>
<textarea class="chat-ta" id="chatInput" placeholder="Message Arjun 2.O..." rows="1" onkeydown="handleKey(event)" oninput="autoGrow(this)"></textarea>
<button class="send-btn" id="sendBtn" onclick="sendMsg()">
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
</div>
</div>
<div class="input-hint">Arjun 2.O is engineered for extreme performance. Developed by Abhay Kumar.</div>
</div>
</main>
<!-- WORKSPACE PANEL -->
<aside class="workspace" id="workspace">
<div class="ws-hdr">
<div class="ws-title"><span>💻</span><span id="wsTitleText">Generated Code</span></div>
<div class="ws-actions">
<button class="ws-icon-btn" onclick="copyWorkspaceCode(this)" id="wsCopyBtn">📋 Copy</button>
<button class="ws-icon-btn ws-close" onclick="closeWorkspace()" title="Close"></button>
</div>
</div>
<div class="ws-content">
<!-- Syntax highlighted code block will appear here -->
<pre><code id="wsCodeBlock" class="hljs"></code></pre>
</div>
</aside>
</div>
</div>
<!-- AUTH MODAL (GAS) -->
<div class="modal-overlay" id="authModal">
<div class="modal-card">
<button class="close-modal" onclick="closeAuthModal()"></button>
<div class="auth-header"><h2>Arjun Network</h2></div>
<div class="auth-tabs">
<div class="auth-tab active" id="tab-login" onclick="switchAuthTab('login')">Login</div>
<div class="auth-tab" id="tab-register" onclick="switchAuthTab('register')">Register</div>
</div>
<!-- Login Form -->
<div id="flow-login">
<div class="auth-step active" id="log-step-1">
<div class="input-group"><input type="email" id="logEmail" placeholder="Registered Email"></div>
<button class="primary-btn" id="btn-log-otp" onclick="processAuth('login_send_otp')">Get Secure OTP</button>
</div>
<div class="auth-step" id="log-step-2">
<div class="input-group"><input type="number" id="logOTP" placeholder="Enter OTP"></div>
<button class="primary-btn" id="btn-log-verify" onclick="processAuth('login_verify')">Verify & Login</button>
<button class="secondary-btn" onclick="switchAuthStep('log-step-2', 'log-step-1')">Back</button>
</div>
</div>
<!-- Register Form -->
<div id="flow-register" style="display:none;">
<div class="auth-step active" id="reg-step-1">
<div class="input-group"><input type="text" id="regName" placeholder="Full Name"></div>
<div class="input-group"><input type="email" id="regEmail" placeholder="Email Address"></div>
<button class="primary-btn" id="btn-reg-otp" onclick="processAuth('register_send_otp')">Send OTP</button>
</div>
<div class="auth-step" id="reg-step-2">
<div class="input-group"><input type="number" id="regOTP" placeholder="Enter Verification OTP"></div>
<button class="primary-btn" id="btn-reg-verify" onclick="processAuth('register_verify')">Create Account</button>
<button class="secondary-btn" onclick="switchAuthStep('reg-step-2', 'reg-step-1')">Back</button>
</div>
</div>
</div>
</div>
<script>
/* ==========================================================================
GLOBAL VARIABLES & CONFIGURATION (वैश्विक चर)
========================================================================== */
// PDF.JS Worker setup for PDF extraction
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
const GAS_URL = "https://script.google.com/macros/s/AKfycbzTsXGoVh97JjtFAAG825GF4WUZqf7FDB3Zbxyf0nrLXt_1HjJSSqA829UcdoamRPvg0A/exec";
const LOGO_URL = "https://i.ibb.co/0y2sdbq6/PT-20260523-133251-0000.png";
let currentUser = localStorage.getItem('arjun_user');
let guestCount = parseInt(localStorage.getItem('arjun_guest') || '0');
window.isStreamingGlobalFlag = false;
// State management object
const S = {
history: [],
pending: null,
busy: false,
isUserScrolling: false,
codeBlocks: [],
currentWorkspaceId: null
};
/* ==========================================================================
INITIALIZATION (शुरुआती प्रक्रिया)
========================================================================== */
document.addEventListener('DOMContentLoaded', () => {
// Show welcome overlay only once
if(!localStorage.getItem('arjun_welcome_seen')) {
document.getElementById('welcomeOverlay').style.display = 'flex';
} else {
document.getElementById('welcomeOverlay').style.display = 'none';
}
updateAuthUI();
// Fetch cloud history if user is logged in
if(currentUser) {
fetchHistoryFromGAS();
} else {
document.getElementById('guestBadge').style.display = 'inline-block';
document.getElementById('guestCountDisplay').innerText = guestCount;
}
// Initialize Speech Synthesis Voices (so it's ready when needed)
if('speechSynthesis' in window) {
window.speechSynthesis.getVoices();
window.speechSynthesis.onvoiceschanged = function() {
window.speechSynthesis.getVoices();
};
}
});
function startArjunApp() {
const overlay = document.getElementById('welcomeOverlay');
overlay.classList.add('hidden');
localStorage.setItem('arjun_welcome_seen', 'true');
setTimeout(() => { overlay.style.display = 'none'; }, 400);
}
function toggleSidebar() { document.getElementById('sidebar').classList.toggle('show'); }
function showToast(msg) {
const container = document.getElementById('toast-container');
const t = document.createElement('div'); t.className = 'toast'; t.innerText = msg;
container.appendChild(t);
setTimeout(() => t.remove(), 3000);
}
/* ==========================================================================
ROBUST COPY FUNCTIONALITY (मजबूत कॉपी फंक्शन - NO COMPROMISE)
========================================================================== */
// Fallback method for devices/browsers that restrict navigator.clipboard
function fallbackCopyTextToClipboard(text, btn) {
var textArea = document.createElement("textarea");
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if(successful) {
const orig = btn.innerHTML;
btn.innerHTML = "✓ Copied";
setTimeout(() => btn.innerHTML = orig, 2000);
} else {
showToast("Failed to copy using fallback!");
}
} catch (err) {
showToast("Error copying text!");
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
}
// Main copy function called from UI
function copyText(btn, text) {
// If modern clipboard API is not available or we are in restricted iframe
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text, btn);
return;
}
// Try modern API
navigator.clipboard.writeText(text).then(function() {
const orig = btn.innerHTML;
btn.innerHTML = "✓ Copied";
setTimeout(() => btn.innerHTML = orig, 2000);
}, function(err) {
// If modern API fails (e.g. permission denied), use fallback
console.warn('Async: Could not copy text, using fallback: ', err);
fallbackCopyTextToClipboard(text, btn);
});
}
function copyWorkspaceCode(btn) {
if(S.currentWorkspaceId !== null) {
const codeText = S.codeBlocks[S.currentWorkspaceId].code;
copyText(btn, codeText);
}
}
/* ==========================================================================
TEXT TO SPEECH: MICROSOFT EDGE (Prabhat Neural)
========================================================================== */
function speakText(text) {
if (!('speechSynthesis' in window)) {
showToast("Text-to-Speech not supported in this browser.");
return;
}
// Cancel any ongoing speech
window.speechSynthesis.cancel();
// Clean markdown symbols from text before speaking
let cleanText = text.replace(/[*_~`#]/g, '').replace(/<[^>]*>?/gm, '');
let utterance = new SpeechSynthesisUtterance(cleanText);
// Default language setting
utterance.lang = 'hi-IN';
utterance.rate = 1.0;
utterance.pitch = 1.0;
let voices = window.speechSynthesis.getVoices();
// 1. Search for Microsoft Prabhat Neural explicitly
let bestVoice = voices.find(v => v.name.includes('Prabhat') && v.name.includes('Neural'));
// 2. Fallback: Search for any Hindi Neural voice
if(!bestVoice) {
bestVoice = voices.find(v => v.lang.includes('hi') && v.name.includes('Neural'));
}
// 3. Fallback: Any Hindi Voice
if(!bestVoice) {
bestVoice = voices.find(v => v.lang.includes('hi-IN'));
}
if (bestVoice) {
utterance.voice = bestVoice;
console.log("Using TTS Voice:", bestVoice.name);
} else {
console.log("Prabhat/Hindi voice not found. Using default system voice.");
}
window.speechSynthesis.speak(utterance);
showToast("🔊 Playing Audio...");
}
/* ==========================================================================
SMART VOICE RECOGNITION (वॉइस टाइपिंग)
========================================================================== */
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = null;
let isRecording = false;
if (SpeechRecognition) {
recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
// 'hi-IN' correctly captures pure Hindi in Devanagari and English words dynamically.
recognition.lang = 'hi-IN';
recognition.onstart = () => {
isRecording = true;
document.getElementById('voiceBtn').classList.add('recording');
};
recognition.onresult = (event) => {
let transcript = '';
for (let i = event.resultIndex; i < event.results.length; ++i) {
transcript += event.results[i][0].transcript;
}
transcript = transcript.trim();
if(transcript.length > 0) {
// Capitalize first letter
transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
// Add punctuation if missing
if (!/[.!?।]$/.test(transcript)) transcript += '.';
transcript += ' ';
}
const inp = document.getElementById('chatInput');
inp.value = (inp.value + ' ' + transcript).trim();
autoGrow(inp);
};
recognition.onend = () => {
isRecording = false;
document.getElementById('voiceBtn').classList.remove('recording');
};
recognition.onerror = (event) => {
showToast("Voice recognition failed: " + event.error);
isRecording = false;
document.getElementById('voiceBtn').classList.remove('recording');
};
}
function toggleVoice() {
if(!recognition) return showToast("Voice input not supported in this browser.");
if(isRecording) recognition.stop(); else recognition.start();
}
/* ==========================================================================
AUTHENTICATION & HISTORY (लॉगिन और डेटा सिंक)
========================================================================== */
function updateAuthUI() {
if (currentUser) {
document.getElementById('loginPromptBtn').style.display = 'none';
document.getElementById('userProfile').style.display = 'flex';
document.getElementById('uEmail').innerText = currentUser;
document.getElementById('uAvatar').innerText = currentUser.charAt(0).toUpperCase();
document.getElementById('guestBadge').style.display = 'none';
} else {
document.getElementById('loginPromptBtn').style.display = 'flex';
document.getElementById('userProfile').style.display = 'none';
}
}
function openAuthModal() { document.getElementById('authModal').style.display = 'flex'; }
function closeAuthModal() { document.getElementById('authModal').style.display = 'none'; }
function switchAuthTab(tab) {
document.querySelectorAll('.auth-tab').forEach(e => e.classList.remove('active'));
document.getElementById(`tab-${tab}`).classList.add('active');
document.getElementById('flow-login').style.display = tab === 'login' ? 'block' : 'none';
document.getElementById('flow-register').style.display = tab === 'register' ? 'block' : 'none';
}
function switchAuthStep(curr, next) {
document.getElementById(curr).classList.remove('active');
document.getElementById(next).classList.add('active');
}
async function processAuth(action) {
let payload = { action }; let btnId = '';
if (action === 'register_send_otp') {
payload.name = document.getElementById('regName').value.trim();
payload.email = document.getElementById('regEmail').value.trim();
payload.phone = "0000000000"; payload.organization = "Arjun Compound";
if(!payload.email || !payload.name) return showToast("Name and Email required.");
btnId = 'btn-reg-otp';
} else if (action === 'login_send_otp') {
payload.email = document.getElementById('logEmail').value.trim();
if(!payload.email) return showToast("Enter email first.");
btnId = 'btn-log-otp';
} else if (action === 'register_verify' || action === 'login_verify') {
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
payload.otp = document.getElementById(action === 'register_verify' ? 'regOTP' : 'logOTP').value.trim();
if(!payload.otp) return showToast("Enter OTP!");
btnId = action === 'register_verify' ? 'btn-reg-verify' : 'btn-log-verify';
}
const btn = document.getElementById(btnId); const ogText = btn.innerHTML;
btn.disabled = true; btn.innerText = 'Processing...';
try {
const res = await fetch(GAS_URL, { method: 'POST', body: JSON.stringify(payload) });
const data = await res.json();
if (data.status === 'success') {
showToast(data.message);
if (action === 'register_send_otp') switchAuthStep('reg-step-1', 'reg-step-2');
else if (action === 'login_send_otp') switchAuthStep('log-step-1', 'log-step-2');
else if (action === 'register_verify' || action === 'login_verify') {
currentUser = payload.email;
localStorage.setItem('arjun_user', currentUser);
closeAuthModal(); updateAuthUI(); fetchHistoryFromGAS();
}
} else {
showToast(data.message);
}
} catch (e) {
showToast("Network Error.");
}
btn.disabled = false; btn.innerHTML = ogText;
}
function logout() { localStorage.removeItem('arjun_user'); location.reload(); }
async function clearMemory() {
if(currentUser) {
showToast("Clearing Cloud History...");
await fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "delete_history", email: currentUser }) });
}
location.reload();
}
async function fetchHistoryFromGAS() {
const m = document.getElementById('msgs');
document.getElementById('welcome').style.display = 'none';
m.innerHTML = '<div style="text-align:center; padding:40px; color:var(--text-muted);">Syncing Secure Data...</div>';
try {
const res = await fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "get_history", email: currentUser }) });
const data = await res.json();
m.innerHTML = '';
if (data.status === 'success' && data.historyJSON !== "[]") {
S.history = JSON.parse(data.historyJSON);
S.history.forEach(msg => {
if(msg.role === 'user') appendUserDOM(msg.content, msg.badge);
else {
window.isStreamingGlobalFlag = false;
appendBotDOM(msg.content, true);
}
});
setTimeout(forceScrollDown, 100);
} else {
document.getElementById('welcome').style.display = 'flex';
}
} catch(e) {
m.innerHTML = ''; document.getElementById('welcome').style.display = 'flex';
}
}
function syncHistory() {
if(currentUser) {
fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "save_history", email: currentUser, historyJSON: JSON.stringify(S.history) }) });
}
}
/* ==========================================================================
ATTACHMENT SYSTEM (मल्टीमॉडल फाइल सपोर्ट)
========================================================================== */
function toggleAttachMenu() {
const menu = document.getElementById('attachMenu');
menu.classList.toggle('show');
}
// Close attachment menu if clicked outside
document.addEventListener('click', function(event) {
const wrapper = document.querySelector('.attach-wrapper');
if (!wrapper.contains(event.target)) {
document.getElementById('attachMenu').classList.remove('show');
}
});
// Trigger hidden file inputs
function triggerInput(type) {
document.getElementById('attachMenu').classList.remove('show');
document.getElementById('inp-' + type).click();
}
async function handleSpecificFile(input, expectedType) {
const file = input.files[0]; if(!file) return;
const fileName = file.name.toLowerCase();
const fileMime = file.type;
// 1. STRICT FORMAT VALIDATION (सख्त जांच)
if (expectedType === 'image' && !fileMime.startsWith('image/')) {
showToast("Invalid Format! Please select an Image file."); input.value=''; return;
}
if (expectedType === 'video' && !fileMime.startsWith('video/')) {
showToast("Invalid Format! Please select a Video file."); input.value=''; return;
}
if (expectedType === 'audio' && !fileMime.startsWith('audio/')) {
showToast("Invalid Format! Please select an Audio file."); input.value=''; return;
}
// Setup UI for file preview
document.getElementById('imgThumb').style.display = 'none';
document.getElementById('fileIcon').style.display = 'none';
document.getElementById('imgName').textContent = file.name;
document.getElementById('imgPrev').classList.add('show');
showToast(`Processing ${file.name}...`);
const reader = new FileReader();
// 2. PROCESS IMAGE
if (expectedType === 'image') {
reader.onload = e => {
S.pending = { type: 'image', data: e.target.result.split(',')[1], name: file.name };
document.getElementById('imgThumb').src = e.target.result;
document.getElementById('imgThumb').style.display = 'flex';
};
reader.readAsDataURL(file);
}
// 3. PROCESS VIDEO / AUDIO (Send directly as Base64 to Model API)
else if (expectedType === 'video' || expectedType === 'audio') {
reader.onload = e => {
S.pending = { type: expectedType, data: e.target.result.split(',')[1], name: file.name };
document.getElementById('fileIcon').textContent = expectedType === 'video' ? '🎥' : '🎵';
document.getElementById('fileIcon').style.display = 'flex';
};
reader.readAsDataURL(file);
}
// 4. PROCESS DOCUMENTS & CODE (Extract Text)
else if (expectedType === 'document') {
document.getElementById('fileIcon').textContent = '📄';
document.getElementById('fileIcon').style.display = 'flex';
// A. PDF Extraction
if (fileName.endsWith('.pdf')) {
reader.onload = async function() {
try {
const typedarray = new Uint8Array(this.result);
const pdf = await pdfjsLib.getDocument(typedarray).promise;
let fullText = "";
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
fullText += textContent.items.map(s => s.str).join(' ') + "\n";
}
S.pending = { type: 'text', data: fullText.trim() || "No text found in PDF.", name: file.name };
showToast("PDF converted to text successfully!");
} catch (e) {
showToast("Error parsing PDF."); removeImg();
}
};
reader.readAsArrayBuffer(file);
}
// B. Word (.docx) Extraction
else if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) {
reader.onload = async function() {
try {
const arrayBuffer = this.result;
const result = await mammoth.extractRawText({arrayBuffer: arrayBuffer});
S.pending = { type: 'text', data: result.value.trim() || "No text found in DOCX.", name: file.name };
showToast("Word document converted to text!");
} catch(e) {
showToast("Error parsing Word doc."); removeImg();
}
};
reader.readAsArrayBuffer(file);
}
// C. HTML, Code, Text Extraction (Direct Read)
else {
reader.onload = e => {
S.pending = { type: 'text', data: e.target.result, name: file.name };
showToast("Code/Text file attached and extracted!");
};
reader.readAsText(file);
}
}
// Reset file input so same file can be selected again
input.value = '';
}
function removeImg(){ S.pending=null; document.getElementById('imgPrev').classList.remove('show'); }
/* ==========================================================================
UI UTILITIES (सुविधाजनक फंक्शन्स)
========================================================================== */
function handleKey(e){ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); sendMsg(); } }
function autoGrow(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,200)+'px'; }
function checkScroll() {
const m = document.getElementById('msgs');
const isAtBottom = Math.abs((m.scrollHeight - m.scrollTop) - m.clientHeight) < 80;
S.isUserScrolling = !isAtBottom;
const btn = document.getElementById('scrollBtn');
if(S.isUserScrolling) btn.classList.add('show'); else btn.classList.remove('show');
}
function forceScrollDown(){
const m = document.getElementById('msgs');
m.scrollTo({top: m.scrollHeight, behavior: 'smooth'});
S.isUserScrolling = false;
document.getElementById('scrollBtn').classList.remove('show');
}
function autoScroll() {
if(!S.isUserScrolling) {
const m = document.getElementById('msgs');
m.scrollTop = m.scrollHeight;
}
}
/* ==========================================================================
WORKSPACE RENDERING & STREAMING UI (कोड वर्कस्पेस रेंडरर)
========================================================================== */
function openWorkspace(id) {
S.currentWorkspaceId = id;
const block = S.codeBlocks[id];
document.getElementById('wsTitleText').textContent = block.lang ? `Generated ${block.lang.toUpperCase()}` : "Generated Code";
const codeBlock = document.getElementById('wsCodeBlock');
codeBlock.className = `hljs language-${block.lang || 'plaintext'}`;
codeBlock.textContent = block.code;
hljs.highlightElement(codeBlock);
document.getElementById('workspace').classList.add('open');
if(window.innerWidth <= 850) {
document.getElementById('sidebar').classList.remove('show');
}
}
function closeWorkspace() { document.getElementById('workspace').classList.remove('open'); }
// MARKED.JS Custom Renderer (handles 'Generating Code' UI logic)
const renderer = new marked.Renderer();
renderer.code = function(token) {
const codeText = typeof token === 'string' ? token : (token.text || '');
const language = typeof token === 'string' ? arguments[1] : (token.lang || '');
// NEW FEATURE: Gemini style "Generating Code..." UI while streaming
if (window.isStreamingGlobalFlag) {
return `
<div class="streaming-code-wrapper">
<div class="streaming-code-header">
<div class="loader-spinner"></div>
Generating Code...
</div>
<pre><code class="language-${language}">${codeText.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre>
</div>`;
}
// When streaming stops, generate the Workspace View Button Card
const id = S.codeBlocks.length;
S.codeBlocks.push({ code: codeText, lang: language });
return `
<div class="ws-trigger-card">
<div class="ws-trigger-info">
<div class="ws-trigger-icon">💻</div>
<div class="ws-trigger-text">
<h4>Code Generated</h4>
<p>${language || 'Snippet'}</p>
</div>
</div>
<button class="ws-btn" onclick="openWorkspace(${id})">View in Workspace</button>
</div>`;
};
marked.use({ renderer });
function parseThinking(raw) {
const thinkRegex = /<think>([\s\S]*?)(<\/think>|$)/gi;
let text = raw, thinkContent = '';
text = text.replace(thinkRegex, (match, p1) => { thinkContent += p1; return ''; });
return { mainText: text.trim(), thinkText: thinkContent.trim() };
}
/* ==========================================================================
MAIN CHAT CONTROLLER (मुख्य चैट लॉजिक)
========================================================================== */
async function sendMsg(){
if(S.busy)return;
const inp = document.getElementById('chatInput');
const msg = inp.value.trim();
if(!msg && !S.pending) return;
// Check Guest Limit
if (!currentUser) {
if (guestCount >= 3) {
openAuthModal();
return showToast("Guest limit reached. Please Login.");
}
guestCount++;
localStorage.setItem('arjun_guest', guestCount.toString());
document.getElementById('guestCountDisplay').innerText = guestCount;
}
inp.value=''; inp.style.height='auto';
document.getElementById('sendBtn').disabled=true;
S.busy=true;
const w = document.getElementById('welcome');
if(w) w.style.display='none';
let backendPayloadMsg = msg;
let attachments = [];
let badge = null;
// Multi-modal attachment routing
if(S.pending) {
if(S.pending.type === 'text') {
// PDF/DOC/Code extracted text
backendPayloadMsg = `[Attached File Data: ${S.pending.name}]\n\n---FILE CONTENT START---\n${S.pending.data}\n---FILE CONTENT END---\n\nUser Question: ${msg}`;
badge = '📄 '+S.pending.name;
} else {
// Raw Image/Audio/Video processing
attachments.push({ type: S.pending.type, data: S.pending.data, name: S.pending.name });
const icon = S.pending.type === 'video' ? '🎥 ' : (S.pending.type === 'audio' ? '🎵 ' : '🖼️ ');
badge = icon + S.pending.name;
}
appendUserDOM(msg || '[File Attached]', badge);
S.history.push({role: 'user', content: backendPayloadMsg, badge: badge});
} else {
appendUserDOM(msg);
S.history.push({role: 'user', content: msg});
}
removeImg();
syncHistory();
const botDiv = document.createElement('div');botDiv.className='msg bot';
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>`;
document.getElementById('msgs').appendChild(botDiv);
forceScrollDown();
// STRICT SYSTEM PROMPT (Bilingual & Pollinations support)
const sysPrompt = `You are Arjun 2.O, a state-of-the-art AI assistant developed by Abhay Kumar.
LANGUAGE PROTOCOL: You are fully bilingual. You MUST write and respond fluently in BOTH English and Hindi.
1. If the user asks in English, reply entirely in English.
2. If the user asks in Hindi, reply in Hindi.
3. If the user uses a mix (Hinglish), you can respond naturally in the same mixed tone.
NEVER force Hindi when the user is speaking English. Always adapt dynamically to the user's language.
CRITICAL IMAGE GENERATION PROTOCOL:
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.
Format to use: ![Arjun 2.O is creating your image...](https://image.pollinations.ai/prompt/{URL_ENCODED_PROMPT}?width=1024&height=1024&nologo=true&model={MODEL})
Available models for {MODEL}: flux, flux-realism, flux-anime, flux-3d.
Choose the best model based on the prompt. Example: if user says anime, use flux-anime.
Always include this markdown tag when image generation is requested.`;
// Stripping extra 'badge' property before sending to backend to avoid API errors
const cleanHistory = S.history.slice(0, -1).map(item => ({ role: item.role, content: item.content }));
const payload = {
message: backendPayloadMsg,
attachments: attachments,
system_prompt: sysPrompt,
history: cleanHistory,
max_tokens: 4096,
temperature: 0.7
};
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error("Server error");
const reader = res.body.getReader();
const decoder = new TextDecoder();
let accumulatedText = "";
window.isStreamingGlobalFlag = true;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, {stream: true});
const lines = chunk.split('\n');
for (let line of lines) {
if (line.startsWith('data: ')) {
const dataStr = line.substring(6);
if (dataStr.trim() === '[DONE]') continue;
try {
const dataObj = JSON.parse(dataStr);
if (dataObj.choices && dataObj.choices[0].delta.content) {
accumulatedText += dataObj.choices[0].delta.content;
// Stream with updating UI (triggers Generating Code visual)
renderBotText(botDiv, accumulatedText, true);
autoScroll();
}
} catch (e) { /* silent catch for partial chunks */ }
}
}
}
window.isStreamingGlobalFlag = false;
// Final render (Transforms "Generating Code" into "Workspace Buttons")
renderBotText(botDiv, accumulatedText, false);
S.history.push({role: 'bot', content: accumulatedText});
syncHistory();
} catch(err) {
window.isStreamingGlobalFlag = false;
renderBotText(botDiv, `**Error:** Server communication failed.`, false);
}
S.busy = false;
document.getElementById('sendBtn').disabled = false;
autoScroll();
}
function sendEx(t){ document.getElementById('chatInput').value=t; sendMsg(); }
function appendUserDOM(text, badgeText){
let bHtml = badgeText ? `<div style="font-size:11px;color:var(--accent);margin-bottom:4px;font-weight:600;">${esc(badgeText)}</div>` : '';
const el = document.createElement('div');
el.className = 'msg user';
el.innerHTML = `<div class="avatar">U</div><div class="body">${bHtml}<div class="bubble">${esc(text)}</div></div>`;
document.getElementById('msgs').appendChild(el);
}
function appendBotDOM(content, isHistoryLoading=false) {
const el = document.createElement('div');
el.className = 'msg bot';
el.innerHTML = `<div class="avatar"><img src="${LOGO_URL}"></div><div class="body"><div class="bubble"></div></div>`;
document.getElementById('msgs').appendChild(el);
renderBotText(el, content, !isHistoryLoading);
}
// Bot DOM Renderer (Text, Markdown, Actions & TTS)
function renderBotText(el, raw, isStreaming) {
const b = el.querySelector('.bubble');
const { mainText, thinkText } = parseThinking(raw);
let html = '';
if (thinkText) {
html += `<div class="think-box"><div class="think-title">🧠 Reasoning Process</div>${esc(thinkText)}</div>`;
}
html += marked.parse(mainText);
if(isStreaming) {
html += '<span class="blinking-cursor"></span>';
}
b.innerHTML = html;
let actionsDiv = el.querySelector('.msg-actions');
// Add action buttons only when streaming is completely done
if(!isStreaming && !actionsDiv && mainText.trim().length > 0) {
// Ensure string is properly escaped for passing into onclick function
const cleanRaw = mainText.replace(/"/g, '&quot;').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
const rawForTTS = mainText.replace(/"/g, '&quot;').replace(/'/g, "\\'").replace(/\n/g, ' ');
const actionsHTML = `
<div class="msg-actions">
<!-- ROBUST COPY BUTTON -->
<button class="action-btn" onclick="copyText(this, \`${cleanRaw}\`)">
<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>
Copy Text
</button>
<!-- TEXT TO SPEECH (PRABHAT NEURAL) BUTTON -->
<button class="action-btn tts-btn" onclick="speakText(\`${rawForTTS}\`)">
<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>
Read Aloud
</button>
</div>`;
el.querySelector('.body').insertAdjacentHTML('beforeend', actionsHTML);
}
}
function esc(t){ return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
</script>
</body>
</html>