Vedika commited on
Commit
57be3ea
ยท
verified ยท
1 Parent(s): dce595c

Update index.html

Browse files
Files changed (1) hide show
  1. 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
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
 
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
- /* --- BASE RESET & VARIABLES --- */
19
- *{margin:0;padding:0;box-sizing:border-box;}
20
- :root{
21
- --bg-color: #ffffff; --bg-secondary: #f9f9fb; --text-main: #1d1d1f; --text-muted: #86868b;
22
- --accent: #FF9933; --accent-green: #138808; --accent-hover: #e68a2e;
23
- --border-light: rgba(0, 0, 0, 0.08); --border-focus: rgba(255, 153, 51, 0.3);
24
- --glass-bg: rgba(255, 255, 255, 0.85); --glass-border: rgba(255, 255, 255, 0.4);
25
- --shadow-sm: 0 2px 8px rgba(0,0,0,0.04); --shadow-md: 0 8px 32px rgba(0,0,0,0.08);
 
 
 
 
 
 
 
 
 
 
26
  --shadow-lg: 0 16px 48px rgba(0,0,0,0.12);
27
- --font-serif: 'Instrument Serif', serif; --font-sans: 'Inter', -apple-system, sans-serif;
 
28
  --font-mono: 'Geist Mono', monospace;
29
- --radius-lg: 24px; --radius-md: 16px; --radius-sm: 8px;
 
 
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
- /* --- BACKGROUND --- */
34
- .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%);}
35
- .orb{position:absolute;border-radius:50%;filter:blur(100px);opacity:0.5;animation:drift 25s ease-in-out infinite alternate;}
36
- .orb1{width:600px;height:600px;background:radial-gradient(circle,rgba(255,153,51,0.2),transparent 70%);top:-150px;left:-100px;}
37
- .orb2{width:500px;height:500px;background:radial-gradient(circle,rgba(19,136,8,0.18),transparent 70%);bottom:-100px;right:-100px;animation-delay:-10s;}
38
- @keyframes drift{0%{transform:translate(0,0) scale(1);}100%{transform:translate(60px,40px) scale(1.05);}}
 
 
39
 
40
- /* --- TOAST NOTIFICATION --- */
 
 
 
 
 
 
 
 
 
 
 
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
- /* --- GET STARTED OVERLAY --- */
 
 
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
- /* --- MAIN LAYOUT --- */
55
- .shell{position:relative;z-index:1;display:flex;height:100dvh;width:100vw;overflow:hidden;}
56
- .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;}
57
- .main-wrapper{flex:1;display:flex;position:relative;overflow:hidden;}
58
- .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%;}
59
- .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;}
60
- .workspace.open{width:50%;}
61
-
62
- /* --- SIDEBAR & BRANDING --- */
63
- .logo-area{padding:24px 20px;border-bottom:1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center;}
64
- .logo-row{display:flex;align-items:center;gap:12px;}
65
- .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;}
66
- .logo-text{line-height:1.2;}
67
- .logo-name{font-family:var(--font-sans);font-weight:800;font-size:16px;color:var(--text-main);letter-spacing:-0.5px;}
68
- .logo-sub{font-size:10px;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--text-muted);}
 
 
 
 
69
  .close-sidebar-btn { display: none; background: none; border: none; font-size: 20px; color: var(--text-main); cursor: pointer; }
70
 
71
- /* Auth Block in Sidebar */
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
- .settings{padding:20px;flex:1;overflow-y:auto;display:flex;flex-direction:column;gap: 20px;}
81
- .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;}
82
- .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);}
83
- .brand-box h3 {font-family: var(--font-sans); font-size: 15px; font-weight: 700; color: var(--text-main); margin-bottom: 6px;}
84
- .brand-box p {font-size: 10px; font-weight: 600; color: var(--accent-green); line-height: 1.4; letter-spacing: 0.5px;}
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
- /* --- CHAT AREA --- */
90
- .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;}
 
 
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
- /* Messages */
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
- .msg-actions{display:flex;gap:8px;margin-top:4px;padding-left:12px;opacity:0.7;transition:opacity 0.2s;}
130
- .msg.bot:hover .msg-actions{opacity:1;}
131
- .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;}
132
- .action-btn:hover{background:#e5e5ea;transform:translateY(-1px);}
133
-
134
- .think-box {display: none;}
135
-
136
- /* Markdown inside bubble */
137
- .bubble p{margin-bottom:12px;}
138
- .bubble p:last-child{margin-bottom:0;}
139
- .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;}
140
- .msg.user .bubble code{background:rgba(255,255,255,0.2);color:#fff;}
141
- .bubble img{max-width:100%;border-radius:12px;margin:10px 0;box-shadow:var(--shadow-sm);border:1px solid var(--border-light);}
142
- .bubble pre {background: #1d1d1f; color:#fff; padding:16px; border-radius:12px; overflow-x:auto;}
143
- .bubble pre code {background: transparent; color:#fff;}
144
-
145
- .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);}
146
- .ws-trigger-info {display: flex;align-items: center;gap: 12px;}
147
- .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);}
148
- .ws-trigger-text h4 {font-size: 14px;font-weight: 600;margin-bottom: 2px;}
149
- .ws-trigger-text p {font-size: 12px;color: var(--text-muted);font-family: var(--font-mono);text-transform: uppercase;}
150
- .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;}
151
- .ws-btn:hover {transform: scale(1.05); background: var(--accent);}
 
 
 
 
 
 
 
 
 
 
 
 
 
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;}.typing span:nth-child(2){animation-delay:-0.16s;}
159
- @keyframes bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);}}
160
-
161
- .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;}
162
- .scroll-bottom-btn.show {opacity:1;pointer-events:auto;transform:translateX(-50%) translateY(0);}
163
- .scroll-bottom-btn:hover {background:#fff;transform:translateX(-50%) translateY(-3px);box-shadow:var(--shadow-lg);}
164
-
165
- /* --- INPUT AREA & ATTACHMENT MENU --- */
166
- .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;}
167
- .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;}
168
- .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);}
169
-
170
- .img-prev-inline{display:none;padding:12px 20px 0;align-items:center;gap:12px;}
171
- .img-prev-inline.show{display:flex;animation:fadeIn 0.3s;}
172
- .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;}
173
- .inline-info{flex:1;}
174
- .inline-nm{font-size:12px;font-weight:600;color:var(--text-main);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:150px;}
175
- .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;}
176
- .inline-rm:hover{background:#ff3b30;color:#fff;}
177
-
178
- .input-row{display:flex;align-items:flex-end;gap:12px;padding:12px 16px 12px 20px;}
179
- .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;}
180
- .chat-ta::placeholder{color:var(--text-muted);}
181
-
182
- /* GEMINI STYLE ATTACHMENT MENU */
 
 
 
 
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.9); 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); }
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
- /* --- WORKSPACE PANEL --- */
206
- .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;}
207
- .ws-title{font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;}
208
- .ws-actions{display:flex;gap:8px;}
209
- .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;}
210
- .ws-icon-btn:hover{background:#e5e5ea;}
211
- .ws-close{padding:6px;background:transparent;border-color:transparent;}
212
- .ws-close:hover{color:#ff3b30;background:#fff0f0;}
213
- .ws-content{flex:1;overflow-y:auto;background:#fafafa;padding:20px;}
214
- .ws-content pre{margin:0;padding:20px;background:#fff;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);}
215
-
216
- /* --- AUTH MODAL (GAS) --- */
 
 
 
 
 
 
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
- <input type="file" id="inp-document" class="hidden-file-input" accept=".pdf,.doc,.docx,.txt,.md,.csv,.json,.py,.js,.html,.css,.cpp,.c" onchange="handleSpecificFile(this, 'document')">
 
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
- // --- PDF.JS INITIALIZATION ---
 
 
 
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
- const S = { history: [], pending: null, busy: false, isUserScrolling: false, codeBlocks: [], currentWorkspaceId: null };
 
 
 
 
 
 
 
 
437
 
 
 
 
438
  document.addEventListener('DOMContentLoaded', () => {
439
- if(!localStorage.getItem('arjun_welcome_seen')) { document.getElementById('welcomeOverlay').style.display = 'flex'; }
440
- else { document.getElementById('welcomeOverlay').style.display = 'none'; }
 
 
 
 
 
441
  updateAuthUI();
442
- if(currentUser) { fetchHistoryFromGAS(); }
443
- else { document.getElementById('guestBadge').style.display = 'inline-block'; document.getElementById('guestCountDisplay').innerText = guestCount; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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); setTimeout(() => t.remove(), 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  }
459
 
460
- // --- VOICE RECOGNITION (SMART HINDI/ENGLISH) ---
 
 
461
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
462
- let recognition = null; let isRecording = false;
 
463
 
464
  if (SpeechRecognition) {
465
  recognition = new SpeechRecognition();
466
  recognition.continuous = false;
467
  recognition.interimResults = false;
468
 
469
- // Set to 'hi-IN'. Google will type pure Hindi in Devanagari and English words in English.
470
  recognition.lang = 'hi-IN';
471
 
472
- recognition.onstart = () => { isRecording = true; document.getElementById('voiceBtn').classList.add('recording'); };
 
 
 
 
473
  recognition.onresult = (event) => {
474
  let transcript = '';
475
- for (let i = event.resultIndex; i < event.results.length; ++i) transcript += event.results[i][0].transcript;
 
 
476
  transcript = transcript.trim();
477
  if(transcript.length > 0) {
 
478
  transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
479
- if (!/[.!?เฅค]$/.test(transcript)) transcript += '.'; transcript += ' ';
 
 
480
  }
481
  const inp = document.getElementById('chatInput');
482
  inp.value = (inp.value + ' ' + transcript).trim();
483
  autoGrow(inp);
484
  };
485
- recognition.onend = () => { isRecording = false; document.getElementById('voiceBtn').classList.remove('recording'); };
486
- recognition.onerror = () => { showToast("Voice recognition failed."); };
 
 
 
 
 
 
 
 
 
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
- // --- AUTH & HISTORY FUNCTIONS (UNCHANGED) ---
494
- 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'; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  function openAuthModal() { document.getElementById('authModal').style.display = 'flex'; }
496
  function closeAuthModal() { document.getElementById('authModal').style.display = 'none'; }
497
- 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'; }
498
- function switchAuthStep(curr, next) { document.getElementById(curr).classList.remove('active'); document.getElementById(next).classList.add('active'); }
499
- 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; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  function logout() { localStorage.removeItem('arjun_user'); location.reload(); }
501
- 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(); }
502
- 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'; } }
503
- function syncHistory() { if(currentUser) { fetch(GAS_URL, { method: 'POST', body: JSON.stringify({ action: "save_history", email: currentUser, historyJSON: JSON.stringify(S.history) }) }); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
 
505
- // --- ADVANCED MULTI-FILE & CONVERTER LOGIC ---
 
 
506
  function toggleAttachMenu() {
507
  const menu = document.getElementById('attachMenu');
508
  menu.classList.toggle('show');
509
  }
510
- // Hide menu when clicked outside
 
511
  document.addEventListener('click', function(event) {
512
  const wrapper = document.querySelector('.attach-wrapper');
513
- if (!wrapper.contains(event.target)) { document.getElementById('attachMenu').classList.remove('show'); }
 
 
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 & INVALID FORMAT TOAST
527
- if (expectedType === 'image' && !fileMime.startsWith('image/')) { showToast("Invalid Format! Please select an Image file."); input.value=''; return; }
528
- if (expectedType === 'video' && !fileMime.startsWith('video/')) { showToast("Invalid Format! Please select a Video file."); input.value=''; return; }
529
- if (expectedType === 'audio' && !fileMime.startsWith('audio/')) { showToast("Invalid Format! Please select an Audio file."); input.value=''; return; }
 
 
 
 
 
 
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. IMAGE, VIDEO, AUDIO (Send as Base64 Media to Model)
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
- // 3. DOCUMENT / CODE (Convert everything to TEXT before sending to Model)
558
  else if (expectedType === 'document') {
559
  document.getElementById('fileIcon').textContent = '๐Ÿ“„';
560
  document.getElementById('fileIcon').style.display = 'flex';
561
 
562
- // A. Handle PDF -> Text via pdf.js
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) { showToast("Error parsing PDF."); removeImg(); }
 
 
577
  };
578
  reader.readAsArrayBuffer(file);
579
  }
580
- // B. Handle Word (.docx) -> Text via Mammoth.js
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) { showToast("Error parsing Word doc."); removeImg(); }
 
 
589
  };
590
  reader.readAsArrayBuffer(file);
591
  }
592
- // C. Handle HTML/Code/Text (.py, .js, .html, .csv etc) -> Text naturally
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
- input.value = ''; // reset
 
 
602
  }
603
 
604
  function removeImg(){ S.pending=null; document.getElementById('imgPrev').classList.remove('show'); }
605
 
606
- // --- UI INTERACTIONS ---
607
- function handleKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}}
608
- function autoGrow(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,200)+'px';}
609
- 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'); }
610
- function forceScrollDown(){ const m=document.getElementById('msgs'); m.scrollTo({top: m.scrollHeight, behavior: 'smooth'}); S.isUserScrolling = false; document.getElementById('scrollBtn').classList.remove('show'); }
611
- function autoScroll() { if(!S.isUserScrolling) { const m=document.getElementById('msgs'); m.scrollTop = m.scrollHeight; } }
612
- function copyText(btn, text) { navigator.clipboard.writeText(text); const orig = btn.innerHTML; btn.innerHTML = "โœ“ Copied"; setTimeout(() => btn.innerHTML = orig, 2000); }
613
- function copyWorkspaceCode() { if(S.currentWorkspaceId !== null) { navigator.clipboard.writeText(S.codeBlocks[S.currentWorkspaceId].code); const btn = document.getElementById('wsCopyBtn'); const orig = btn.innerHTML; btn.innerHTML = "โœ“ Copied"; setTimeout(() => btn.innerHTML = orig, 2000); } }
614
-
615
- // --- WORKSPACE LOGIC ---
616
- 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'); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  function closeWorkspace() { document.getElementById('workspace').classList.remove('open'); }
 
 
618
  const renderer = new marked.Renderer();
619
  renderer.code = function(token) {
620
- const codeText = typeof token === 'string' ? token : (token.text || '');
621
- const language = typeof token === 'string' ? arguments[1] : (token.lang || '');
622
- if (window.isStreamingGlobalFlag) { return `<pre><code class="language-${language}">${codeText.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre>`; }
623
- const id = S.codeBlocks.length; S.codeBlocks.push({ code: codeText, lang: language });
624
- 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>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  };
626
  marked.use({ renderer });
627
 
@@ -632,117 +1051,210 @@ function parseThinking(raw) {
632
  return { mainText: text.trim(), thinkText: thinkContent.trim() };
633
  }
634
 
635
- // --- MAIN CHAT LOGIC ---
 
 
636
  async function sendMsg(){
637
- if(S.busy)return;
638
- const inp=document.getElementById('chatInput');
639
- const msg=inp.value.trim();if(!msg && !S.pending)return;
640
-
641
- if (!currentUser) {
642
- if (guestCount >= 3) { openAuthModal(); return showToast("Guest limit reached. Please Login."); }
643
- guestCount++; localStorage.setItem('arjun_guest', guestCount.toString());
644
- document.getElementById('guestCountDisplay').innerText = guestCount;
645
- }
646
-
647
- inp.value='';inp.style.height='auto';
648
- document.getElementById('sendBtn').disabled=true;
649
- S.busy=true;
 
 
 
 
 
 
650
 
651
- const w=document.getElementById('welcome');
652
- if(w)w.style.display='none';
653
-
654
- let backendPayloadMsg = msg;
655
- let attachments = [];
656
- let badge = null;
657
-
658
- if(S.pending) {
659
- if(S.pending.type === 'text') {
660
- // Send Extracted Code/PDF text
661
- backendPayloadMsg = `[Attached File Data: ${S.pending.name}]\n${S.pending.data}\n\nUser Question: ${msg}`;
662
- badge = '๐Ÿ“„ '+S.pending.name;
663
- } else {
664
- // Send raw media data for Image, Audio, Video
665
- attachments.push({ type: S.pending.type, data: S.pending.data, name: S.pending.name });
666
- const icon = S.pending.type === 'video' ? '๐ŸŽฅ ' : (S.pending.type === 'audio' ? '๐ŸŽต ' : '๐Ÿ–ผ๏ธ ');
667
- badge = icon + S.pending.name;
668
- }
669
- appendUserDOM(msg || '[File Attached]', badge);
670
- S.history.push({role: 'user', content: backendPayloadMsg, badge: badge});
671
- } else {
672
- appendUserDOM(msg);
673
- S.history.push({role: 'user', content: msg});
674
- }
 
675
 
676
- removeImg(); syncHistory();
677
-
678
- const botDiv = document.createElement('div');botDiv.className='msg bot';
679
- 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>`;
680
- document.getElementById('msgs').appendChild(botDiv);
681
- forceScrollDown();
682
-
683
- const sysPrompt = `You are Arjun 2.O, a state-of-the-art AI assistant developed by Abhay Kumar.
684
- LANGUAGE PROTOCOL: You are fully bilingual. You MUST write and respond fluently in BOTH English and Hindi.
685
- 1. If the user asks in English, reply entirely in English.
686
- 2. If the user asks in Hindi, reply in Hindi.
687
- 3. If the user uses a mix (Hinglish), you can respond naturally in the same mixed tone.
688
- NEVER force Hindi when the user is speaking English. Always adapt dynamically to the user's language.
 
 
689
 
690
- CRITICAL IMAGE GENERATION PROTOCOL:
691
- 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.
692
- 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})
693
- Available models for {MODEL}: flux, flux-realism, flux-anime, flux-3d.
694
- Choose the best model based on the prompt. Example: if user says anime, use flux-anime.
695
- Always include this markdown tag when image generation is requested.`;
696
 
697
- const cleanHistory = S.history.slice(0, -1).map(item => ({ role: item.role, content: item.content }));
698
-
699
- const payload = {
700
- message: backendPayloadMsg,
701
- attachments: attachments,
702
- system_prompt: sysPrompt,
703
- history: cleanHistory,
704
- max_tokens: 4096,
705
- temperature: 0.7
706
- };
707
-
708
- try{
709
- const res = await fetch('/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) });
710
- if (!res.ok) throw new Error("Server error");
711
- const reader = res.body.getReader(); const decoder = new TextDecoder();
712
- let accumulatedText = "";
713
- window.isStreamingGlobalFlag = true;
714
- while (true) {
715
- const { done, value } = await reader.read();
716
- if (done) break;
717
- const chunk = decoder.decode(value, {stream: true});
718
- const lines = chunk.split('\n');
719
- for (let line of lines) {
720
- if (line.startsWith('data: ')) {
721
- const dataStr = line.substring(6);
722
- if (dataStr.trim() === '[DONE]') continue;
723
- try {
724
- const dataObj = JSON.parse(dataStr);
725
- if (dataObj.choices && dataObj.choices[0].delta.content) {
726
- accumulatedText += dataObj.choices[0].delta.content;
727
- renderBotText(botDiv, accumulatedText, true); autoScroll();
728
- }
729
- } catch (e) { }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  }
731
  }
 
 
 
 
 
 
 
 
 
 
 
732
  }
733
- window.isStreamingGlobalFlag = false;
734
- renderBotText(botDiv, accumulatedText, false);
735
- S.history.push({role: 'bot', content: accumulatedText});
736
- syncHistory();
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
- 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); }
743
- 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); }
744
- 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'); if(!isStreaming && !actionsDiv && mainText.trim().length > 0) { const cleanRaw = mainText.replace(/"/g, '&quot;').replace(/'/g, "\\'").replace(/\n/g, '\\n'); const actionsHTML = `<div class="msg-actions"><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></div>`; el.querySelector('.body').insertAdjacentHTML('beforeend', actionsHTML); } }
745
- function esc(t){return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, '&lt;').replace(/>/g, '&gt;')}</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: ![Arjun 2.O is creating your image...](https://image.pollinations.ai/prompt/{URL_ENCODED_PROMPT}?width=1024&height=1024&nologo=true&model={MODEL})
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, '&quot;').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
1236
+ const rawForTTS = mainText.replace(/"/g, '&quot;').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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
1257
+
1258
  </script>
1259
  </body>
1260
  </html>