Rox-Turbo commited on
Commit
6800ca4
·
verified ·
1 Parent(s): 46ec487

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +1918 -0
index.html ADDED
@@ -0,0 +1,1918 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
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
+ <meta name="description" content="Rox AI - Next-generation conversational AI interface">
7
+ <meta name="theme-color" content="#667eea">
8
+ <meta name="apple-mobile-web-app-capable" content="yes">
9
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
+
11
+ <title>Rox AI | Intelligent Conversations</title>
12
+
13
+ <!-- Preconnect for performance -->
14
+ <link rel="preconnect" href="https://Rox-Turbo-API.hf.space">
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
18
+
19
+ <style>
20
+ :root {
21
+ /* Design System - Colors */
22
+ --primary-50: #eef2ff;
23
+ --primary-100: #e0e7ff;
24
+ --primary-200: #c7d2fe;
25
+ --primary-300: #a5b4fc;
26
+ --primary-400: #818cf8;
27
+ --primary-500: #667eea;
28
+ --primary-600: #5b67d6;
29
+ --primary-700: #4f46e5;
30
+ --primary-800: #3730a3;
31
+ --primary-900: #312e81;
32
+
33
+ --accent-purple: #764ba2;
34
+ --accent-pink: #f093fb;
35
+ --accent-blue: #4facfe;
36
+
37
+ /* Neutral Scale */
38
+ --gray-50: #f9fafb;
39
+ --gray-100: #f3f4f6;
40
+ --gray-200: #e5e7eb;
41
+ --gray-300: #d1d5db;
42
+ --gray-400: #9ca3af;
43
+ --gray-500: #6b7280;
44
+ --gray-600: #4b5563;
45
+ --gray-700: #374151;
46
+ --gray-800: #1f2937;
47
+ --gray-900: #111827;
48
+
49
+ /* Semantic Colors */
50
+ --success: #10b981;
51
+ --warning: #f59e0b;
52
+ --error: #ef4444;
53
+ --info: #3b82f6;
54
+
55
+ /* Spacing System */
56
+ --space-1: 0.25rem;
57
+ --space-2: 0.5rem;
58
+ --space-3: 0.75rem;
59
+ --space-4: 1rem;
60
+ --space-5: 1.25rem;
61
+ --space-6: 1.5rem;
62
+ --space-8: 2rem;
63
+ --space-10: 2.5rem;
64
+ --space-12: 3rem;
65
+ --space-16: 4rem;
66
+
67
+ /* Typography */
68
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
69
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
70
+
71
+ /* Shadows */
72
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
73
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
74
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
75
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
76
+ --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
77
+ --shadow-glow: 0 0 20px rgba(102, 126, 234, 0.5);
78
+
79
+ /* Transitions */
80
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
81
+ --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
82
+ --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
83
+ --transition-spring: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
84
+
85
+ /* Border Radius */
86
+ --radius-sm: 0.375rem;
87
+ --radius-md: 0.5rem;
88
+ --radius-lg: 0.75rem;
89
+ --radius-xl: 1rem;
90
+ --radius-2xl: 1.5rem;
91
+ --radius-full: 9999px;
92
+
93
+ /* Z-Index Scale */
94
+ --z-dropdown: 100;
95
+ --z-sticky: 200;
96
+ --z-modal: 300;
97
+ --z-tooltip: 400;
98
+ --z-toast: 500;
99
+ }
100
+
101
+ /* Dark Mode Support */
102
+ @media (prefers-color-scheme: dark) {
103
+ :root {
104
+ --bg-primary: var(--gray-900);
105
+ --bg-secondary: var(--gray-800);
106
+ --bg-tertiary: var(--gray-700);
107
+ --text-primary: var(--gray-50);
108
+ --text-secondary: var(--gray-300);
109
+ --text-tertiary: var(--gray-400);
110
+ --border-color: var(--gray-700);
111
+ }
112
+ }
113
+
114
+ @media (prefers-color-scheme: light) {
115
+ :root {
116
+ --bg-primary: #ffffff;
117
+ --bg-secondary: var(--gray-50);
118
+ --bg-tertiary: var(--gray-100);
119
+ --text-primary: var(--gray-900);
120
+ --text-secondary: var(--gray-600);
121
+ --text-tertiary: var(--gray-400);
122
+ --border-color: var(--gray-200);
123
+ }
124
+ }
125
+
126
+ /* Reset & Base */
127
+ *, *::before, *::after {
128
+ box-sizing: border-box;
129
+ margin: 0;
130
+ padding: 0;
131
+ }
132
+
133
+ html {
134
+ font-size: 16px;
135
+ -webkit-font-smoothing: antialiased;
136
+ -moz-osx-font-smoothing: grayscale;
137
+ text-rendering: optimizeLegibility;
138
+ scroll-behavior: smooth;
139
+ }
140
+
141
+ body {
142
+ font-family: var(--font-sans);
143
+ background: linear-gradient(135deg, var(--primary-500) 0%, var(--accent-purple) 50%, var(--primary-700) 100%);
144
+ background-attachment: fixed;
145
+ min-height: 100vh;
146
+ color: var(--text-primary);
147
+ line-height: 1.6;
148
+ overflow-x: hidden;
149
+ }
150
+
151
+ /* Animated Background */
152
+ .ambient-bg {
153
+ position: fixed;
154
+ top: 0;
155
+ left: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ pointer-events: none;
159
+ z-index: -1;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .ambient-bg::before,
164
+ .ambient-bg::after {
165
+ content: '';
166
+ position: absolute;
167
+ border-radius: 50%;
168
+ filter: blur(80px);
169
+ opacity: 0.4;
170
+ animation: float 20s infinite ease-in-out;
171
+ }
172
+
173
+ .ambient-bg::before {
174
+ width: 600px;
175
+ height: 600px;
176
+ background: var(--accent-pink);
177
+ top: -200px;
178
+ right: -100px;
179
+ animation-delay: 0s;
180
+ }
181
+
182
+ .ambient-bg::after {
183
+ width: 500px;
184
+ height: 500px;
185
+ background: var(--accent-blue);
186
+ bottom: -150px;
187
+ left: -100px;
188
+ animation-delay: -10s;
189
+ }
190
+
191
+ @keyframes float {
192
+ 0%, 100% { transform: translate(0, 0) scale(1); }
193
+ 33% { transform: translate(30px, -30px) scale(1.1); }
194
+ 66% { transform: translate(-20px, 20px) scale(0.9); }
195
+ }
196
+
197
+ /* App Container */
198
+ .app-container {
199
+ display: grid;
200
+ grid-template-columns: 280px 1fr;
201
+ grid-template-rows: 1fr;
202
+ height: 100vh;
203
+ max-width: 1600px;
204
+ margin: 0 auto;
205
+ background: var(--bg-primary);
206
+ box-shadow: var(--shadow-2xl);
207
+ overflow: hidden;
208
+ }
209
+
210
+ @media (max-width: 1024px) {
211
+ .app-container {
212
+ grid-template-columns: 1fr;
213
+ }
214
+ }
215
+
216
+ /* Sidebar */
217
+ .sidebar {
218
+ background: var(--bg-secondary);
219
+ border-right: 1px solid var(--border-color);
220
+ display: flex;
221
+ flex-direction: column;
222
+ padding: var(--space-6);
223
+ gap: var(--space-6);
224
+ overflow-y: auto;
225
+ }
226
+
227
+ @media (max-width: 1024px) {
228
+ .sidebar {
229
+ position: fixed;
230
+ left: -100%;
231
+ top: 0;
232
+ height: 100vh;
233
+ width: 280px;
234
+ z-index: var(--z-modal);
235
+ transition: left var(--transition-slow);
236
+ }
237
+
238
+ .sidebar.open {
239
+ left: 0;
240
+ }
241
+ }
242
+
243
+ .sidebar-header {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: var(--space-3);
247
+ padding-bottom: var(--space-6);
248
+ border-bottom: 1px solid var(--border-color);
249
+ }
250
+
251
+ .logo {
252
+ width: 40px;
253
+ height: 40px;
254
+ background: linear-gradient(135deg, var(--primary-500), var(--accent-purple));
255
+ border-radius: var(--radius-lg);
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ font-size: 1.25rem;
260
+ color: white;
261
+ box-shadow: var(--shadow-md);
262
+ }
263
+
264
+ .brand-text h1 {
265
+ font-size: 1.25rem;
266
+ font-weight: 700;
267
+ color: var(--text-primary);
268
+ letter-spacing: -0.025em;
269
+ }
270
+
271
+ .brand-text p {
272
+ font-size: 0.75rem;
273
+ color: var(--text-tertiary);
274
+ font-weight: 500;
275
+ }
276
+
277
+ /* New Chat Button */
278
+ .btn-new-chat {
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ gap: var(--space-2);
283
+ padding: var(--space-3) var(--space-4);
284
+ background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
285
+ color: white;
286
+ border: none;
287
+ border-radius: var(--radius-lg);
288
+ font-size: 0.875rem;
289
+ font-weight: 600;
290
+ cursor: pointer;
291
+ transition: all var(--transition-base);
292
+ box-shadow: var(--shadow-md);
293
+ }
294
+
295
+ .btn-new-chat:hover {
296
+ transform: translateY(-2px);
297
+ box-shadow: var(--shadow-lg), var(--shadow-glow);
298
+ }
299
+
300
+ .btn-new-chat:active {
301
+ transform: translateY(0);
302
+ }
303
+
304
+ /* Conversation History */
305
+ .history-section {
306
+ flex: 1;
307
+ display: flex;
308
+ flex-direction: column;
309
+ gap: var(--space-2);
310
+ }
311
+
312
+ .section-title {
313
+ font-size: 0.75rem;
314
+ font-weight: 600;
315
+ text-transform: uppercase;
316
+ letter-spacing: 0.05em;
317
+ color: var(--text-tertiary);
318
+ padding: 0 var(--space-2);
319
+ }
320
+
321
+ .conversation-list {
322
+ display: flex;
323
+ flex-direction: column;
324
+ gap: var(--space-1);
325
+ }
326
+
327
+ .conversation-item {
328
+ display: flex;
329
+ align-items: center;
330
+ gap: var(--space-3);
331
+ padding: var(--space-3) var(--space-4);
332
+ border-radius: var(--radius-lg);
333
+ cursor: pointer;
334
+ transition: all var(--transition-fast);
335
+ color: var(--text-secondary);
336
+ font-size: 0.875rem;
337
+ font-weight: 500;
338
+ }
339
+
340
+ .conversation-item:hover {
341
+ background: var(--bg-tertiary);
342
+ color: var(--text-primary);
343
+ }
344
+
345
+ .conversation-item.active {
346
+ background: var(--primary-50);
347
+ color: var(--primary-700);
348
+ }
349
+
350
+ .conversation-item svg {
351
+ width: 18px;
352
+ height: 18px;
353
+ flex-shrink: 0;
354
+ }
355
+
356
+ /* Settings Panel */
357
+ .settings-panel {
358
+ border-top: 1px solid var(--border-color);
359
+ padding-top: var(--space-6);
360
+ display: flex;
361
+ flex-direction: column;
362
+ gap: var(--space-4);
363
+ }
364
+
365
+ .setting-group {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: var(--space-2);
369
+ }
370
+
371
+ .setting-label {
372
+ font-size: 0.75rem;
373
+ font-weight: 600;
374
+ color: var(--text-tertiary);
375
+ text-transform: uppercase;
376
+ letter-spacing: 0.05em;
377
+ }
378
+
379
+ .model-selector {
380
+ position: relative;
381
+ }
382
+
383
+ .model-select {
384
+ width: 100%;
385
+ padding: var(--space-3) var(--space-4);
386
+ padding-right: var(--space-10);
387
+ background: var(--bg-tertiary);
388
+ border: 1px solid var(--border-color);
389
+ border-radius: var(--radius-lg);
390
+ font-size: 0.875rem;
391
+ color: var(--text-primary);
392
+ cursor: pointer;
393
+ appearance: none;
394
+ transition: all var(--transition-fast);
395
+ }
396
+
397
+ .model-select:hover {
398
+ border-color: var(--primary-300);
399
+ }
400
+
401
+ .model-select:focus {
402
+ outline: none;
403
+ border-color: var(--primary-500);
404
+ box-shadow: 0 0 0 3px var(--primary-100);
405
+ }
406
+
407
+ .model-selector::after {
408
+ content: '▼';
409
+ position: absolute;
410
+ right: var(--space-4);
411
+ top: 50%;
412
+ transform: translateY(-50%);
413
+ font-size: 0.625rem;
414
+ color: var(--text-tertiary);
415
+ pointer-events: none;
416
+ }
417
+
418
+ /* Toggle Switch */
419
+ .toggle-group {
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: space-between;
423
+ }
424
+
425
+ .toggle-label {
426
+ font-size: 0.875rem;
427
+ color: var(--text-secondary);
428
+ font-weight: 500;
429
+ }
430
+
431
+ .toggle-switch {
432
+ position: relative;
433
+ width: 44px;
434
+ height: 24px;
435
+ background: var(--gray-300);
436
+ border-radius: var(--radius-full);
437
+ cursor: pointer;
438
+ transition: background var(--transition-fast);
439
+ }
440
+
441
+ .toggle-switch.active {
442
+ background: var(--primary-500);
443
+ }
444
+
445
+ .toggle-switch::after {
446
+ content: '';
447
+ position: absolute;
448
+ top: 2px;
449
+ left: 2px;
450
+ width: 20px;
451
+ height: 20px;
452
+ background: white;
453
+ border-radius: 50%;
454
+ transition: transform var(--transition-spring);
455
+ box-shadow: var(--shadow-sm);
456
+ }
457
+
458
+ .toggle-switch.active::after {
459
+ transform: translateX(20px);
460
+ }
461
+
462
+ /* Main Chat Area */
463
+ .main-content {
464
+ display: flex;
465
+ flex-direction: column;
466
+ height: 100vh;
467
+ position: relative;
468
+ }
469
+
470
+ /* Mobile Header */
471
+ .mobile-header {
472
+ display: none;
473
+ align-items: center;
474
+ justify-content: space-between;
475
+ padding: var(--space-4);
476
+ background: var(--bg-primary);
477
+ border-bottom: 1px solid var(--border-color);
478
+ }
479
+
480
+ @media (max-width: 1024px) {
481
+ .mobile-header {
482
+ display: flex;
483
+ }
484
+ }
485
+
486
+ .menu-btn {
487
+ width: 40px;
488
+ height: 40px;
489
+ display: flex;
490
+ align-items: center;
491
+ justify-content: center;
492
+ background: var(--bg-secondary);
493
+ border: none;
494
+ border-radius: var(--radius-lg);
495
+ cursor: pointer;
496
+ color: var(--text-secondary);
497
+ transition: all var(--transition-fast);
498
+ }
499
+
500
+ .menu-btn:hover {
501
+ background: var(--bg-tertiary);
502
+ color: var(--text-primary);
503
+ }
504
+
505
+ /* Chat Header */
506
+ .chat-header {
507
+ display: flex;
508
+ align-items: center;
509
+ justify-content: space-between;
510
+ padding: var(--space-4) var(--space-6);
511
+ background: var(--bg-primary);
512
+ border-bottom: 1px solid var(--border-color);
513
+ }
514
+
515
+ .chat-info {
516
+ display: flex;
517
+ align-items: center;
518
+ gap: var(--space-3);
519
+ }
520
+
521
+ .model-badge {
522
+ display: inline-flex;
523
+ align-items: center;
524
+ gap: var(--space-2);
525
+ padding: var(--space-1) var(--space-3);
526
+ background: var(--primary-50);
527
+ color: var(--primary-700);
528
+ font-size: 0.75rem;
529
+ font-weight: 600;
530
+ border-radius: var(--radius-full);
531
+ border: 1px solid var(--primary-200);
532
+ }
533
+
534
+ .status-indicator {
535
+ width: 8px;
536
+ height: 8px;
537
+ background: var(--success);
538
+ border-radius: 50%;
539
+ animation: pulse 2s infinite;
540
+ }
541
+
542
+ @keyframes pulse {
543
+ 0%, 100% { opacity: 1; }
544
+ 50% { opacity: 0.5; }
545
+ }
546
+
547
+ .header-actions {
548
+ display: flex;
549
+ gap: var(--space-2);
550
+ }
551
+
552
+ .icon-btn {
553
+ width: 36px;
554
+ height: 36px;
555
+ display: flex;
556
+ align-items: center;
557
+ justify-content: center;
558
+ background: transparent;
559
+ border: 1px solid var(--border-color);
560
+ border-radius: var(--radius-lg);
561
+ cursor: pointer;
562
+ color: var(--text-secondary);
563
+ transition: all var(--transition-fast);
564
+ }
565
+
566
+ .icon-btn:hover {
567
+ background: var(--bg-secondary);
568
+ color: var(--text-primary);
569
+ border-color: var(--primary-300);
570
+ }
571
+
572
+ /* Messages Container */
573
+ .messages-container {
574
+ flex: 1;
575
+ overflow-y: auto;
576
+ padding: var(--space-6);
577
+ scroll-behavior: smooth;
578
+ }
579
+
580
+ .messages-container::-webkit-scrollbar {
581
+ width: 8px;
582
+ }
583
+
584
+ .messages-container::-webkit-scrollbar-track {
585
+ background: transparent;
586
+ }
587
+
588
+ .messages-container::-webkit-scrollbar-thumb {
589
+ background: var(--gray-300);
590
+ border-radius: var(--radius-full);
591
+ }
592
+
593
+ .messages-container::-webkit-scrollbar-thumb:hover {
594
+ background: var(--gray-400);
595
+ }
596
+
597
+ .welcome-screen {
598
+ display: flex;
599
+ flex-direction: column;
600
+ align-items: center;
601
+ justify-content: center;
602
+ height: 100%;
603
+ text-align: center;
604
+ gap: var(--space-6);
605
+ padding: var(--space-8);
606
+ }
607
+
608
+ .welcome-icon {
609
+ width: 80px;
610
+ height: 80px;
611
+ background: linear-gradient(135deg, var(--primary-500), var(--accent-purple));
612
+ border-radius: var(--radius-2xl);
613
+ display: flex;
614
+ align-items: center;
615
+ justify-content: center;
616
+ font-size: 2.5rem;
617
+ box-shadow: var(--shadow-xl);
618
+ animation: bounce-in var(--transition-spring);
619
+ }
620
+
621
+ @keyframes bounce-in {
622
+ 0% { transform: scale(0); opacity: 0; }
623
+ 50% { transform: scale(1.1); }
624
+ 100% { transform: scale(1); opacity: 1; }
625
+ }
626
+
627
+ .welcome-title {
628
+ font-size: 2rem;
629
+ font-weight: 700;
630
+ color: var(--text-primary);
631
+ letter-spacing: -0.025em;
632
+ }
633
+
634
+ .welcome-subtitle {
635
+ font-size: 1rem;
636
+ color: var(--text-secondary);
637
+ max-width: 400px;
638
+ }
639
+
640
+ .suggestion-chips {
641
+ display: flex;
642
+ flex-wrap: wrap;
643
+ gap: var(--space-3);
644
+ justify-content: center;
645
+ margin-top: var(--space-4);
646
+ }
647
+
648
+ .suggestion-chip {
649
+ padding: var(--space-3) var(--space-5);
650
+ background: var(--bg-secondary);
651
+ border: 1px solid var(--border-color);
652
+ border-radius: var(--radius-full);
653
+ font-size: 0.875rem;
654
+ color: var(--text-secondary);
655
+ cursor: pointer;
656
+ transition: all var(--transition-fast);
657
+ }
658
+
659
+ .suggestion-chip:hover {
660
+ background: var(--primary-50);
661
+ border-color: var(--primary-300);
662
+ color: var(--primary-700);
663
+ transform: translateY(-2px);
664
+ }
665
+
666
+ /* Message Styles */
667
+ .message {
668
+ display: flex;
669
+ gap: var(--space-4);
670
+ margin-bottom: var(--space-6);
671
+ animation: message-in var(--transition-base);
672
+ max-width: 100%;
673
+ }
674
+
675
+ @keyframes message-in {
676
+ from {
677
+ opacity: 0;
678
+ transform: translateY(10px);
679
+ }
680
+ to {
681
+ opacity: 1;
682
+ transform: translateY(0);
683
+ }
684
+ }
685
+
686
+ .message.user {
687
+ flex-direction: row-reverse;
688
+ }
689
+
690
+ .message-avatar {
691
+ width: 36px;
692
+ height: 36px;
693
+ border-radius: var(--radius-lg);
694
+ display: flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ flex-shrink: 0;
698
+ font-size: 1rem;
699
+ }
700
+
701
+ .message.user .message-avatar {
702
+ background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
703
+ color: white;
704
+ }
705
+
706
+ .message.assistant .message-avatar {
707
+ background: var(--bg-tertiary);
708
+ color: var(--text-secondary);
709
+ }
710
+
711
+ .message-content-wrapper {
712
+ display: flex;
713
+ flex-direction: column;
714
+ gap: var(--space-2);
715
+ max-width: calc(100% - 60px);
716
+ }
717
+
718
+ .message.user .message-content-wrapper {
719
+ align-items: flex-end;
720
+ }
721
+
722
+ .message-header {
723
+ display: flex;
724
+ align-items: center;
725
+ gap: var(--space-2);
726
+ font-size: 0.75rem;
727
+ color: var(--text-tertiary);
728
+ }
729
+
730
+ .message-content {
731
+ padding: var(--space-4) var(--space-5);
732
+ border-radius: var(--radius-xl);
733
+ font-size: 0.9375rem;
734
+ line-height: 1.7;
735
+ word-wrap: break-word;
736
+ position: relative;
737
+ }
738
+
739
+ .message.user .message-content {
740
+ background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
741
+ color: white;
742
+ border-bottom-right-radius: var(--radius-sm);
743
+ box-shadow: var(--shadow-md);
744
+ }
745
+
746
+ .message.assistant .message-content {
747
+ background: var(--bg-secondary);
748
+ color: var(--text-primary);
749
+ border: 1px solid var(--border-color);
750
+ border-bottom-left-radius: var(--radius-sm);
751
+ }
752
+
753
+ /* Code Blocks */
754
+ .code-block {
755
+ background: var(--gray-900);
756
+ border-radius: var(--radius-lg);
757
+ margin: var(--space-4) 0;
758
+ overflow: hidden;
759
+ }
760
+
761
+ .code-header {
762
+ display: flex;
763
+ align-items: center;
764
+ justify-content: space-between;
765
+ padding: var(--space-3) var(--space-4);
766
+ background: var(--gray-800);
767
+ border-bottom: 1px solid var(--gray-700);
768
+ }
769
+
770
+ .code-language {
771
+ font-size: 0.75rem;
772
+ font-weight: 600;
773
+ color: var(--gray-400);
774
+ text-transform: uppercase;
775
+ }
776
+
777
+ .code-actions {
778
+ display: flex;
779
+ gap: var(--space-2);
780
+ }
781
+
782
+ .code-btn {
783
+ padding: var(--space-1) var(--space-3);
784
+ background: var(--gray-700);
785
+ border: none;
786
+ border-radius: var(--radius-md);
787
+ font-size: 0.75rem;
788
+ color: var(--gray-300);
789
+ cursor: pointer;
790
+ transition: all var(--transition-fast);
791
+ font-family: var(--font-sans);
792
+ }
793
+
794
+ .code-btn:hover {
795
+ background: var(--gray-600);
796
+ color: white;
797
+ }
798
+
799
+ .code-content {
800
+ padding: var(--space-4);
801
+ overflow-x: auto;
802
+ font-family: var(--font-mono);
803
+ font-size: 0.875rem;
804
+ line-height: 1.6;
805
+ color: #e5e7eb;
806
+ }
807
+
808
+ .code-content pre {
809
+ margin: 0;
810
+ }
811
+
812
+ /* Inline Code */
813
+ code {
814
+ font-family: var(--font-mono);
815
+ font-size: 0.875em;
816
+ background: var(--bg-tertiary);
817
+ padding: 0.2em 0.4em;
818
+ border-radius: var(--radius-md);
819
+ color: var(--primary-600);
820
+ }
821
+
822
+ .message.user code {
823
+ background: rgba(255, 255, 255, 0.2);
824
+ color: white;
825
+ }
826
+
827
+ /* Typing Indicator */
828
+ .typing-indicator {
829
+ display: flex;
830
+ gap: var(--space-4);
831
+ margin-bottom: var(--space-6);
832
+ opacity: 0;
833
+ transition: opacity var(--transition-fast);
834
+ }
835
+
836
+ .typing-indicator.visible {
837
+ opacity: 1;
838
+ }
839
+
840
+ .typing-bubbles {
841
+ display: flex;
842
+ align-items: center;
843
+ gap: 4px;
844
+ padding: var(--space-4) var(--space-5);
845
+ background: var(--bg-secondary);
846
+ border: 1px solid var(--border-color);
847
+ border-radius: var(--radius-xl);
848
+ border-bottom-left-radius: var(--radius-sm);
849
+ }
850
+
851
+ .typing-bubble {
852
+ width: 8px;
853
+ height: 8px;
854
+ background: var(--primary-400);
855
+ border-radius: 50%;
856
+ animation: typing-bounce 1.4s infinite ease-in-out both;
857
+ }
858
+
859
+ .typing-bubble:nth-child(1) { animation-delay: -0.32s; }
860
+ .typing-bubble:nth-child(2) { animation-delay: -0.16s; }
861
+
862
+ @keyframes typing-bounce {
863
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
864
+ 40% { transform: scale(1); opacity: 1; }
865
+ }
866
+
867
+ /* Input Area */
868
+ .input-container {
869
+ padding: var(--space-4) var(--space-6);
870
+ background: var(--bg-primary);
871
+ border-top: 1px solid var(--border-color);
872
+ }
873
+
874
+ .input-wrapper {
875
+ display: flex;
876
+ gap: var(--space-3);
877
+ align-items: flex-end;
878
+ background: var(--bg-secondary);
879
+ border: 1px solid var(--border-color);
880
+ border-radius: var(--radius-xl);
881
+ padding: var(--space-3);
882
+ transition: all var(--transition-fast);
883
+ }
884
+
885
+ .input-wrapper:focus-within {
886
+ border-color: var(--primary-300);
887
+ box-shadow: 0 0 0 3px var(--primary-100);
888
+ background: var(--bg-primary);
889
+ }
890
+
891
+ .input-actions {
892
+ display: flex;
893
+ gap: var(--space-2);
894
+ padding-bottom: var(--space-1);
895
+ }
896
+
897
+ .input-btn {
898
+ width: 32px;
899
+ height: 32px;
900
+ display: flex;
901
+ align-items: center;
902
+ justify-content: center;
903
+ background: transparent;
904
+ border: none;
905
+ border-radius: var(--radius-lg);
906
+ cursor: pointer;
907
+ color: var(--text-tertiary);
908
+ transition: all var(--transition-fast);
909
+ flex-shrink: 0;
910
+ }
911
+
912
+ .input-btn:hover {
913
+ background: var(--bg-tertiary);
914
+ color: var(--text-primary);
915
+ }
916
+
917
+ .message-input {
918
+ flex: 1;
919
+ background: transparent;
920
+ border: none;
921
+ resize: none;
922
+ font-family: var(--font-sans);
923
+ font-size: 0.9375rem;
924
+ line-height: 1.6;
925
+ color: var(--text-primary);
926
+ max-height: 200px;
927
+ min-height: 24px;
928
+ padding: var(--space-1) 0;
929
+ }
930
+
931
+ .message-input:focus {
932
+ outline: none;
933
+ }
934
+
935
+ .message-input::placeholder {
936
+ color: var(--text-tertiary);
937
+ }
938
+
939
+ .send-btn {
940
+ width: 36px;
941
+ height: 36px;
942
+ display: flex;
943
+ align-items: center;
944
+ justify-content: center;
945
+ background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
946
+ border: none;
947
+ border-radius: var(--radius-lg);
948
+ cursor: pointer;
949
+ color: white;
950
+ transition: all var(--transition-fast);
951
+ flex-shrink: 0;
952
+ box-shadow: var(--shadow-md);
953
+ }
954
+
955
+ .send-btn:hover:not(:disabled) {
956
+ transform: scale(1.05);
957
+ box-shadow: var(--shadow-lg);
958
+ }
959
+
960
+ .send-btn:disabled {
961
+ opacity: 0.5;
962
+ cursor: not-allowed;
963
+ transform: none;
964
+ }
965
+
966
+ .input-footer {
967
+ display: flex;
968
+ justify-content: space-between;
969
+ align-items: center;
970
+ margin-top: var(--space-2);
971
+ padding: 0 var(--space-2);
972
+ font-size: 0.75rem;
973
+ color: var(--text-tertiary);
974
+ }
975
+
976
+ .shortcut-hint {
977
+ display: flex;
978
+ gap: var(--space-3);
979
+ }
980
+
981
+ kbd {
982
+ font-family: var(--font-mono);
983
+ padding: 0.125rem 0.375rem;
984
+ background: var(--bg-tertiary);
985
+ border-radius: var(--radius-md);
986
+ font-size: 0.75rem;
987
+ border: 1px solid var(--border-color);
988
+ }
989
+
990
+ /* Toast Notifications */
991
+ .toast-container {
992
+ position: fixed;
993
+ bottom: var(--space-6);
994
+ right: var(--space-6);
995
+ display: flex;
996
+ flex-direction: column;
997
+ gap: var(--space-3);
998
+ z-index: var(--z-toast);
999
+ pointer-events: none;
1000
+ }
1001
+
1002
+ .toast {
1003
+ display: flex;
1004
+ align-items: center;
1005
+ gap: var(--space-3);
1006
+ padding: var(--space-4) var(--space-5);
1007
+ background: var(--gray-800);
1008
+ color: white;
1009
+ border-radius: var(--radius-lg);
1010
+ box-shadow: var(--shadow-xl);
1011
+ font-size: 0.875rem;
1012
+ font-weight: 500;
1013
+ animation: toast-in var(--transition-spring);
1014
+ pointer-events: auto;
1015
+ }
1016
+
1017
+ @keyframes toast-in {
1018
+ from {
1019
+ transform: translateX(100%);
1020
+ opacity: 0;
1021
+ }
1022
+ to {
1023
+ transform: translateX(0);
1024
+ opacity: 1;
1025
+ }
1026
+ }
1027
+
1028
+ .toast.success { border-left: 4px solid var(--success); }
1029
+ .toast.error { border-left: 4px solid var(--error); }
1030
+ .toast.info { border-left: 4px solid var(--info); }
1031
+
1032
+ /* Overlay for mobile sidebar */
1033
+ .sidebar-overlay {
1034
+ display: none;
1035
+ position: fixed;
1036
+ inset: 0;
1037
+ background: rgba(0, 0, 0, 0.5);
1038
+ z-index: calc(var(--z-modal) - 1);
1039
+ backdrop-filter: blur(4px);
1040
+ }
1041
+
1042
+ @media (max-width: 1024px) {
1043
+ .sidebar-overlay.visible {
1044
+ display: block;
1045
+ }
1046
+ }
1047
+
1048
+ /* Empty State */
1049
+ .empty-state {
1050
+ text-align: center;
1051
+ padding: var(--space-12);
1052
+ color: var(--text-tertiary);
1053
+ }
1054
+
1055
+ /* Loading State */
1056
+ .skeleton {
1057
+ background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-secondary) 50%, var(--bg-tertiary) 75%);
1058
+ background-size: 200% 100%;
1059
+ animation: shimmer 1.5s infinite;
1060
+ border-radius: var(--radius-md);
1061
+ }
1062
+
1063
+ @keyframes shimmer {
1064
+ 0% { background-position: 200% 0; }
1065
+ 100% { background-position: -200% 0; }
1066
+ }
1067
+
1068
+ /* Responsive Adjustments */
1069
+ @media (max-width: 640px) {
1070
+ .messages-container {
1071
+ padding: var(--space-4);
1072
+ }
1073
+
1074
+ .message-content {
1075
+ padding: var(--space-3) var(--space-4);
1076
+ font-size: 0.875rem;
1077
+ }
1078
+
1079
+ .welcome-title {
1080
+ font-size: 1.5rem;
1081
+ }
1082
+
1083
+ .input-container {
1084
+ padding: var(--space-3) var(--space-4);
1085
+ }
1086
+ }
1087
+
1088
+ /* Print Styles */
1089
+ @media print {
1090
+ .sidebar, .input-container, .chat-header {
1091
+ display: none;
1092
+ }
1093
+
1094
+ .messages-container {
1095
+ overflow: visible;
1096
+ }
1097
+ }
1098
+
1099
+ /* Reduced Motion */
1100
+ @media (prefers-reduced-motion: reduce) {
1101
+ *, *::before, *::after {
1102
+ animation-duration: 0.01ms !important;
1103
+ animation-iteration-count: 1 !important;
1104
+ transition-duration: 0.01ms !important;
1105
+ }
1106
+ }
1107
+
1108
+ /* Focus Visible */
1109
+ :focus-visible {
1110
+ outline: 2px solid var(--primary-500);
1111
+ outline-offset: 2px;
1112
+ }
1113
+
1114
+ /* Selection */
1115
+ ::selection {
1116
+ background: var(--primary-200);
1117
+ color: var(--primary-900);
1118
+ }
1119
+ </style>
1120
+ </head>
1121
+ <body>
1122
+ <div class="ambient-bg"></div>
1123
+
1124
+ <div class="app-container">
1125
+ <!-- Sidebar -->
1126
+ <aside class="sidebar" id="sidebar">
1127
+ <div class="sidebar-header">
1128
+ <div class="logo">🚀</div>
1129
+ <div class="brand-text">
1130
+ <h1>Rox AI</h1>
1131
+ <p>Next-Gen Intelligence</p>
1132
+ </div>
1133
+ </div>
1134
+
1135
+ <button class="btn-new-chat" onclick="chatApp.newConversation()">
1136
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1137
+ <line x1="12" y1="5" x2="12" y2="19"></line>
1138
+ <line x1="5" y1="12" x2="19" y2="12"></line>
1139
+ </svg>
1140
+ New Conversation
1141
+ </button>
1142
+
1143
+ <div class="history-section">
1144
+ <span class="section-title">Recent Conversations</span>
1145
+ <div class="conversation-list" id="conversationList">
1146
+ <!-- Populated by JS -->
1147
+ </div>
1148
+ </div>
1149
+
1150
+ <div class="settings-panel">
1151
+ <div class="setting-group">
1152
+ <span class="setting-label">Model</span>
1153
+ <div class="model-selector">
1154
+ <select class="model-select" id="modelSelect">
1155
+ <option value="chat">Rox Core (Balanced)</option>
1156
+ <option value="turbo">Rox Turbo (Fast)</option>
1157
+ <option value="coder">Rox Coder (Code)</option>
1158
+ <option value="turbo45">Rox 4.5 Turbo (Advanced)</option>
1159
+ <option value="ultra">Rox Ultra (Powerful)</option>
1160
+ <option value="dyno">Rox Dyno (Dynamic)</option>
1161
+ <option value="coder7">Rox 7 Coder (Enterprise)</option>
1162
+ <option value="vision">Rox Vision (Multimodal)</option>
1163
+ </select>
1164
+ </div>
1165
+ </div>
1166
+
1167
+ <div class="setting-group">
1168
+ <div class="toggle-group">
1169
+ <span class="toggle-label">Stream Responses</span>
1170
+ <div class="toggle-switch active" id="streamToggle" onclick="chatApp.toggleStreaming()"></div>
1171
+ </div>
1172
+ </div>
1173
+
1174
+ <div class="setting-group">
1175
+ <div class="toggle-group">
1176
+ <span class="toggle-label">Auto-scroll</span>
1177
+ <div class="toggle-switch active" id="autoScrollToggle" onclick="chatApp.toggleAutoScroll()"></div>
1178
+ </div>
1179
+ </div>
1180
+ </div>
1181
+ </aside>
1182
+
1183
+ <!-- Main Content -->
1184
+ <main class="main-content">
1185
+ <!-- Mobile Header -->
1186
+ <div class="mobile-header">
1187
+ <button class="menu-btn" onclick="chatApp.toggleSidebar()">
1188
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1189
+ <line x1="3" y1="12" x2="21" y2="12"></line>
1190
+ <line x1="3" y1="6" x2="21" y2="6"></line>
1191
+ <line x1="3" y1="18" x2="21" y2="18"></line>
1192
+ </svg>
1193
+ </button>
1194
+ <div class="chat-info">
1195
+ <span class="model-badge" id="mobileModelBadge">
1196
+ <span class="status-indicator"></span>
1197
+ Rox Core
1198
+ </span>
1199
+ </div>
1200
+ <button class="icon-btn" onclick="chatApp.newConversation()">
1201
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1202
+ <line x1="12" y1="5" x2="12" y2="19"></line>
1203
+ <line x1="5" y1="12" x2="19" y2="12"></line>
1204
+ </svg>
1205
+ </button>
1206
+ </div>
1207
+
1208
+ <!-- Chat Header -->
1209
+ <div class="chat-header">
1210
+ <div class="chat-info">
1211
+ <span class="model-badge" id="modelBadge">
1212
+ <span class="status-indicator"></span>
1213
+ <span id="currentModelName">Rox Core</span>
1214
+ </span>
1215
+ </div>
1216
+ <div class="header-actions">
1217
+ <button class="icon-btn" onclick="chatApp.exportConversation()" title="Export">
1218
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1219
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
1220
+ <polyline points="7 10 12 15 17 10"></polyline>
1221
+ <line x1="12" y1="15" x2="12" y2="3"></line>
1222
+ </svg>
1223
+ </button>
1224
+ <button class="icon-btn" onclick="chatApp.clearConversation()" title="Clear">
1225
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1226
+ <polyline points="3 6 5 6 21 6"></polyline>
1227
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
1228
+ </svg>
1229
+ </button>
1230
+ </div>
1231
+ </div>
1232
+
1233
+ <!-- Messages -->
1234
+ <div class="messages-container" id="messagesContainer">
1235
+ <div class="welcome-screen" id="welcomeScreen">
1236
+ <div class="welcome-icon">🚀</div>
1237
+ <h2 class="welcome-title">Welcome to Rox AI</h2>
1238
+ <p class="welcome-subtitle">Experience next-generation AI conversations. Select a model and start chatting.</p>
1239
+ <div class="suggestion-chips">
1240
+ <button class="suggestion-chip" onclick="chatApp.sendSuggestion('Explain quantum computing in simple terms')">
1241
+ Explain quantum computing
1242
+ </button>
1243
+ <button class="suggestion-chip" onclick="chatApp.sendSuggestion('Write a Python function to calculate fibonacci')">
1244
+ Write Python code
1245
+ </button>
1246
+ <button class="suggestion-chip" onclick="chatApp.sendSuggestion('Help me brainstorm ideas for a sci-fi novel')">
1247
+ Brainstorm ideas
1248
+ </button>
1249
+ <button class="suggestion-chip" onclick="chatApp.sendSuggestion('Analyze the implications of AI in healthcare')">
1250
+ AI analysis
1251
+ </button>
1252
+ </div>
1253
+ </div>
1254
+ <div id="messagesList" style="display: none;"></div>
1255
+ </div>
1256
+
1257
+ <!-- Input -->
1258
+ <div class="input-container">
1259
+ <div class="input-wrapper">
1260
+ <div class="input-actions">
1261
+ <button class="input-btn" title="Attach file">
1262
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1263
+ <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
1264
+ </svg>
1265
+ </button>
1266
+ </div>
1267
+ <textarea
1268
+ class="message-input"
1269
+ id="messageInput"
1270
+ placeholder="Message Rox AI..."
1271
+ rows="1"
1272
+ oninput="chatApp.autoResize(this)"
1273
+ onkeydown="chatApp.handleKeydown(event)"
1274
+ ></textarea>
1275
+ <button class="send-btn" id="sendBtn" onclick="chatApp.sendMessage()">
1276
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1277
+ <line x1="22" y1="2" x2="11" y2="13"></line>
1278
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
1279
+ </svg>
1280
+ </button>
1281
+ </div>
1282
+ <div class="input-footer">
1283
+ <span>Rox AI can make mistakes. Consider checking important information.</span>
1284
+ <div class="shortcut-hint">
1285
+ <span><kbd>Shift</kbd> + <kbd>Enter</kbd> for new line</span>
1286
+ <span><kbd>Enter</kbd> to send</span>
1287
+ </div>
1288
+ </div>
1289
+ </div>
1290
+ </main>
1291
+ </div>
1292
+
1293
+ <!-- Mobile Sidebar Overlay -->
1294
+ <div class="sidebar-overlay" id="sidebarOverlay" onclick="chatApp.toggleSidebar()"></div>
1295
+
1296
+ <!-- Toast Container -->
1297
+ <div class="toast-container" id="toastContainer"></div>
1298
+
1299
+ <script>
1300
+ /**
1301
+ * Rox AI - Production Grade Chat Application
1302
+ * Architecture: Modular ES6+ Class-based design with state management
1303
+ */
1304
+
1305
+ class ChatApplication {
1306
+ constructor() {
1307
+ // Configuration
1308
+ this.config = {
1309
+ apiBaseUrl: 'https://Rox-Turbo-API.hf.space',
1310
+ maxRetries: 3,
1311
+ retryDelay: 1000,
1312
+ maxHistory: 50,
1313
+ debounceMs: 150
1314
+ };
1315
+
1316
+ // State Management
1317
+ this.state = {
1318
+ currentConversationId: null,
1319
+ conversations: new Map(),
1320
+ isStreaming: true,
1321
+ autoScroll: true,
1322
+ isProcessing: false,
1323
+ currentModel: 'chat',
1324
+ sidebarOpen: false
1325
+ };
1326
+
1327
+ // DOM Elements Cache
1328
+ this.elements = {
1329
+ messagesContainer: document.getElementById('messagesContainer'),
1330
+ messagesList: document.getElementById('messagesList'),
1331
+ welcomeScreen: document.getElementById('welcomeScreen'),
1332
+ messageInput: document.getElementById('messageInput'),
1333
+ sendBtn: document.getElementById('sendBtn'),
1334
+ modelSelect: document.getElementById('modelSelect'),
1335
+ streamToggle: document.getElementById('streamToggle'),
1336
+ autoScrollToggle: document.getElementById('autoScrollToggle'),
1337
+ conversationList: document.getElementById('conversationList'),
1338
+ sidebar: document.getElementById('sidebar'),
1339
+ sidebarOverlay: document.getElementById('sidebarOverlay'),
1340
+ modelBadge: document.getElementById('modelBadge'),
1341
+ currentModelName: document.getElementById('currentModelName'),
1342
+ mobileModelBadge: document.getElementById('mobileModelBadge'),
1343
+ toastContainer: document.getElementById('toastContainer')
1344
+ };
1345
+
1346
+ // Initialize
1347
+ this.init();
1348
+ }
1349
+
1350
+ init() {
1351
+ this.loadConversations();
1352
+ this.setupEventListeners();
1353
+ this.renderConversationList();
1354
+
1355
+ // Focus input on load
1356
+ setTimeout(() => this.elements.messageInput.focus(), 100);
1357
+ }
1358
+
1359
+ setupEventListeners() {
1360
+ // Model selection
1361
+ this.elements.modelSelect.addEventListener('change', (e) => {
1362
+ this.state.currentModel = e.target.value;
1363
+ this.updateModelDisplay();
1364
+ });
1365
+
1366
+ // Window resize
1367
+ window.addEventListener('resize', this.debounce(() => {
1368
+ this.scrollToBottom();
1369
+ }, this.config.debounceMs));
1370
+
1371
+ // Before unload - save state
1372
+ window.addEventListener('beforeunload', () => {
1373
+ this.saveConversations();
1374
+ });
1375
+ }
1376
+
1377
+ // ==================== Core Messaging ====================
1378
+
1379
+ async sendMessage() {
1380
+ if (this.state.isProcessing) return;
1381
+
1382
+ const input = this.elements.messageInput;
1383
+ const message = input.value.trim();
1384
+
1385
+ if (!message) return;
1386
+
1387
+ // Initialize conversation if needed
1388
+ if (!this.state.currentConversationId) {
1389
+ this.createNewConversation();
1390
+ }
1391
+
1392
+ // Add user message
1393
+ this.addMessage('user', message);
1394
+
1395
+ // Clear input
1396
+ input.value = '';
1397
+ input.style.height = 'auto';
1398
+ this.updateSendButton();
1399
+
1400
+ // Update conversation history
1401
+ const conversation = this.state.conversations.get(this.state.currentConversationId);
1402
+ conversation.messages.push({ role: 'user', content: message, timestamp: Date.now() });
1403
+ conversation.title = this.generateTitle(conversation.messages);
1404
+
1405
+ // Show typing indicator
1406
+ this.showTypingIndicator();
1407
+
1408
+ // Process based on streaming preference
1409
+ try {
1410
+ if (this.state.isStreaming) {
1411
+ await this.handleStreamingResponse(conversation);
1412
+ } else {
1413
+ await this.handleStandardResponse(conversation);
1414
+ }
1415
+
1416
+ this.saveConversations();
1417
+ this.renderConversationList();
1418
+ } catch (error) {
1419
+ this.handleError(error);
1420
+ } finally {
1421
+ this.hideTypingIndicator();
1422
+ this.state.isProcessing = false;
1423
+ this.updateSendButton();
1424
+ }
1425
+ }
1426
+
1427
+ async handleStreamingResponse(conversation) {
1428
+ const apiUrl = `${this.config.apiBaseUrl}/${this.state.currentModel}`;
1429
+ let retryCount = 0;
1430
+
1431
+ while (retryCount < this.config.maxRetries) {
1432
+ try {
1433
+ const response = await fetch(apiUrl, {
1434
+ method: 'POST',
1435
+ headers: {
1436
+ 'Content-Type': 'application/json',
1437
+ 'Accept': 'text/event-stream'
1438
+ },
1439
+ body: JSON.stringify({
1440
+ messages: conversation.messages.slice(-this.config.maxHistory),
1441
+ temperature: 0.7,
1442
+ top_p: 0.95,
1443
+ max_tokens: 8192,
1444
+ stream: true
1445
+ })
1446
+ });
1447
+
1448
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1449
+
1450
+ const reader = response.body.getReader();
1451
+ const decoder = new TextDecoder();
1452
+ let fullContent = '';
1453
+ let messageElement = null;
1454
+
1455
+ while (true) {
1456
+ const { done, value } = await reader.read();
1457
+ if (done) break;
1458
+
1459
+ const chunk = decoder.decode(value, { stream: true });
1460
+ const lines = chunk.split('\n');
1461
+
1462
+ for (const line of lines) {
1463
+ if (line.startsWith('data: ')) {
1464
+ const data = line.slice(6).trim();
1465
+ if (data === '[DONE]') continue;
1466
+
1467
+ try {
1468
+ const parsed = JSON.parse(data);
1469
+ if (parsed.content) {
1470
+ if (!messageElement) {
1471
+ messageElement = this.addMessage('assistant', '');
1472
+ }
1473
+ fullContent += parsed.content;
1474
+ this.updateMessageContent(messageElement, fullContent);
1475
+
1476
+ if (this.state.autoScroll) {
1477
+ this.scrollToBottom();
1478
+ }
1479
+ }
1480
+ } catch (e) {
1481
+ // Ignore parse errors in stream
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+
1487
+ if (fullContent) {
1488
+ conversation.messages.push({
1489
+ role: 'assistant',
1490
+ content: fullContent,
1491
+ timestamp: Date.now()
1492
+ });
1493
+ }
1494
+
1495
+ return;
1496
+
1497
+ } catch (error) {
1498
+ retryCount++;
1499
+ if (retryCount >= this.config.maxRetries) throw error;
1500
+ await this.delay(this.config.retryDelay * retryCount);
1501
+ }
1502
+ }
1503
+ }
1504
+
1505
+ async handleStandardResponse(conversation) {
1506
+ const apiUrl = `${this.config.apiBaseUrl}/${this.state.currentModel}`;
1507
+
1508
+ const response = await fetch(apiUrl, {
1509
+ method: 'POST',
1510
+ headers: { 'Content-Type': 'application/json' },
1511
+ body: JSON.stringify({
1512
+ messages: conversation.messages.slice(-this.config.maxHistory),
1513
+ temperature: 0.7,
1514
+ top_p: 0.95,
1515
+ max_tokens: 8192,
1516
+ stream: false
1517
+ })
1518
+ });
1519
+
1520
+ if (!response.ok) {
1521
+ const error = await response.json().catch(() => ({}));
1522
+ throw new Error(error.detail || `HTTP ${response.status}`);
1523
+ }
1524
+
1525
+ const data = await response.json();
1526
+
1527
+ if (data.content) {
1528
+ this.addMessage('assistant', data.content);
1529
+ conversation.messages.push({
1530
+ role: 'assistant',
1531
+ content: data.content,
1532
+ timestamp: Date.now()
1533
+ });
1534
+ }
1535
+ }
1536
+
1537
+ // ==================== UI Management ====================
1538
+
1539
+ addMessage(role, content) {
1540
+ // Hide welcome screen, show messages
1541
+ this.elements.welcomeScreen.style.display = 'none';
1542
+ this.elements.messagesList.style.display = 'block';
1543
+
1544
+ const messageDiv = document.createElement('div');
1545
+ messageDiv.className = `message ${role}`;
1546
+
1547
+ const avatar = document.createElement('div');
1548
+ avatar.className = 'message-avatar';
1549
+ avatar.textContent = role === 'user' ? '👤' : '🤖';
1550
+
1551
+ const wrapper = document.createElement('div');
1552
+ wrapper.className = 'message-content-wrapper';
1553
+
1554
+ const header = document.createElement('div');
1555
+ header.className = 'message-header';
1556
+ header.textContent = role === 'user' ? 'You' : 'Rox AI';
1557
+
1558
+ const time = document.createElement('span');
1559
+ time.textContent = '• ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1560
+ header.appendChild(time);
1561
+
1562
+ const contentDiv = document.createElement('div');
1563
+ contentDiv.className = 'message-content';
1564
+
1565
+ if (role === 'assistant') {
1566
+ contentDiv.innerHTML = this.formatContent(content);
1567
+ } else {
1568
+ contentDiv.textContent = content;
1569
+ }
1570
+
1571
+ wrapper.appendChild(header);
1572
+ wrapper.appendChild(contentDiv);
1573
+ messageDiv.appendChild(avatar);
1574
+ messageDiv.appendChild(wrapper);
1575
+
1576
+ this.elements.messagesList.appendChild(messageDiv);
1577
+
1578
+ if (this.state.autoScroll) {
1579
+ this.scrollToBottom();
1580
+ }
1581
+
1582
+ return contentDiv;
1583
+ }
1584
+
1585
+ updateMessageContent(element, content) {
1586
+ element.innerHTML = this.formatContent(content);
1587
+ }
1588
+
1589
+ formatContent(content) {
1590
+ // Simple markdown-like formatting
1591
+ let formatted = content
1592
+ .replace(/&/g, '&amp;')
1593
+ .replace(/</g, '&lt;')
1594
+ .replace(/>/g, '&gt;');
1595
+
1596
+ // Code blocks
1597
+ formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
1598
+ const language = lang || 'text';
1599
+ return `
1600
+ <div class="code-block">
1601
+ <div class="code-header">
1602
+ <span class="code-language">${language}</span>
1603
+ <div class="code-actions">
1604
+ <button class="code-btn" onclick="chatApp.copyCode(this)">Copy</button>
1605
+ </div>
1606
+ </div>
1607
+ <div class="code-content"><pre>${code.trim()}</pre></div>
1608
+ </div>
1609
+ `;
1610
+ });
1611
+
1612
+ // Inline code
1613
+ formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
1614
+
1615
+ // Bold
1616
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
1617
+
1618
+ // Italic
1619
+ formatted = formatted.replace(/\*(.*?)\*/g, '<em>$1</em>');
1620
+
1621
+ // Line breaks
1622
+ formatted = formatted.replace(/\n/g, '<br>');
1623
+
1624
+ return formatted;
1625
+ }
1626
+
1627
+ showTypingIndicator() {
1628
+ const indicator = document.createElement('div');
1629
+ indicator.className = 'typing-indicator visible';
1630
+ indicator.id = 'typingIndicator';
1631
+ indicator.innerHTML = `
1632
+ <div class="message-avatar">🤖</div>
1633
+ <div class="typing-bubbles">
1634
+ <div class="typing-bubble"></div>
1635
+ <div class="typing-bubble"></div>
1636
+ <div class="typing-bubble"></div>
1637
+ </div>
1638
+ `;
1639
+ this.elements.messagesList.appendChild(indicator);
1640
+ this.scrollToBottom();
1641
+ }
1642
+
1643
+ hideTypingIndicator() {
1644
+ const indicator = document.getElementById('typingIndicator');
1645
+ if (indicator) indicator.remove();
1646
+ }
1647
+
1648
+ // ==================== Conversation Management ====================
1649
+
1650
+ createNewConversation() {
1651
+ const id = 'conv_' + Date.now();
1652
+ const conversation = {
1653
+ id,
1654
+ title: 'New Conversation',
1655
+ messages: [],
1656
+ model: this.state.currentModel,
1657
+ createdAt: Date.now(),
1658
+ updatedAt: Date.now()
1659
+ };
1660
+
1661
+ this.state.conversations.set(id, conversation);
1662
+ this.state.currentConversationId = id;
1663
+ this.renderConversationList();
1664
+
1665
+ return conversation;
1666
+ }
1667
+
1668
+ newConversation() {
1669
+ this.state.currentConversationId = null;
1670
+ this.elements.messagesList.innerHTML = '';
1671
+ this.elements.messagesList.style.display = 'none';
1672
+ this.elements.welcomeScreen.style.display = 'flex';
1673
+ this.elements.messageInput.value = '';
1674
+ this.elements.messageInput.style.height = 'auto';
1675
+ this.elements.messageInput.focus();
1676
+
1677
+ if (window.innerWidth <= 1024) {
1678
+ this.toggleSidebar();
1679
+ }
1680
+ }
1681
+
1682
+ loadConversation(id) {
1683
+ const conversation = this.state.conversations.get(id);
1684
+ if (!conversation) return;
1685
+
1686
+ this.state.currentConversationId = id;
1687
+ this.state.currentModel = conversation.model || 'chat';
1688
+ this.elements.modelSelect.value = this.state.currentModel;
1689
+ this.updateModelDisplay();
1690
+
1691
+ // Render messages
1692
+ this.elements.messagesList.innerHTML = '';
1693
+ this.elements.welcomeScreen.style.display = 'none';
1694
+ this.elements.messagesList.style.display = 'block';
1695
+
1696
+ conversation.messages.forEach(msg => {
1697
+ if (msg.role === 'user') {
1698
+ this.addMessage('user', msg.content);
1699
+ } else {
1700
+ const element = this.addMessage('assistant', '');
1701
+ this.updateMessageContent(element, msg.content);
1702
+ }
1703
+ });
1704
+
1705
+ this.scrollToBottom();
1706
+
1707
+ if (window.innerWidth <= 1024) {
1708
+ this.toggleSidebar();
1709
+ }
1710
+ }
1711
+
1712
+ clearConversation() {
1713
+ if (!this.state.currentConversationId) return;
1714
+
1715
+ if (confirm('Clear all messages in this conversation?')) {
1716
+ const conversation = this.state.conversations.get(this.state.currentConversationId);
1717
+ conversation.messages = [];
1718
+ this.elements.messagesList.innerHTML = '';
1719
+ this.saveConversations();
1720
+ }
1721
+ }
1722
+
1723
+ exportConversation() {
1724
+ if (!this.state.currentConversationId) {
1725
+ this.showToast('No conversation to export', 'error');
1726
+ return;
1727
+ }
1728
+
1729
+ const conversation = this.state.conversations.get(this.state.currentConversationId);
1730
+ const exportData = {
1731
+ title: conversation.title,
1732
+ model: conversation.model,
1733
+ exportedAt: new Date().toISOString(),
1734
+ messages: conversation.messages
1735
+ };
1736
+
1737
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
1738
+ const url = URL.createObjectURL(blob);
1739
+ const a = document.createElement('a');
1740
+ a.href = url;
1741
+ a.download = `rox-ai-conversation-${Date.now()}.json`;
1742
+ a.click();
1743
+ URL.revokeObjectURL(url);
1744
+
1745
+ this.showToast('Conversation exported', 'success');
1746
+ }
1747
+
1748
+ // ==================== UI Helpers ====================
1749
+
1750
+ toggleSidebar() {
1751
+ this.state.sidebarOpen = !this.state.sidebarOpen;
1752
+ this.elements.sidebar.classList.toggle('open', this.state.sidebarOpen);
1753
+ this.elements.sidebarOverlay.classList.toggle('visible', this.state.sidebarOpen);
1754
+ }
1755
+
1756
+ toggleStreaming() {
1757
+ this.state.isStreaming = !this.state.isStreaming;
1758
+ this.elements.streamToggle.classList.toggle('active', this.state.isStreaming);
1759
+ this.showToast(this.state.isStreaming ? 'Streaming enabled' : 'Streaming disabled', 'info');
1760
+ }
1761
+
1762
+ toggleAutoScroll() {
1763
+ this.state.autoScroll = !this.state.autoScroll;
1764
+ this.elements.autoScrollToggle.classList.toggle('active', this.state.autoScroll);
1765
+ }
1766
+
1767
+ updateModelDisplay() {
1768
+ const modelNames = {
1769
+ 'chat': 'Rox Core',
1770
+ 'turbo': 'Rox Turbo',
1771
+ 'coder': 'Rox Coder',
1772
+ 'turbo45': 'Rox 4.5 Turbo',
1773
+ 'ultra': 'Rox Ultra',
1774
+ 'dyno': 'Rox Dyno',
1775
+ 'coder7': 'Rox 7 Coder',
1776
+ 'vision': 'Rox Vision'
1777
+ };
1778
+
1779
+ const name = modelNames[this.state.currentModel] || 'Rox Core';
1780
+ this.elements.currentModelName.textContent = name;
1781
+ this.elements.mobileModelBadge.innerHTML = `<span class="status-indicator"></span>${name}`;
1782
+ }
1783
+
1784
+ updateSendButton() {
1785
+ this.elements.sendBtn.disabled = this.state.isProcessing || !this.elements.messageInput.value.trim();
1786
+ }
1787
+
1788
+ autoResize(textarea) {
1789
+ textarea.style.height = 'auto';
1790
+ textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
1791
+ this.updateSendButton();
1792
+ }
1793
+
1794
+ handleKeydown(event) {
1795
+ if (event.key === 'Enter' && !event.shiftKey) {
1796
+ event.preventDefault();
1797
+ this.sendMessage();
1798
+ }
1799
+ }
1800
+
1801
+ sendSuggestion(text) {
1802
+ this.elements.messageInput.value = text;
1803
+ this.autoResize(this.elements.messageInput);
1804
+ this.sendMessage();
1805
+ }
1806
+
1807
+ scrollToBottom() {
1808
+ this.elements.messagesContainer.scrollTop = this.elements.messagesContainer.scrollHeight;
1809
+ }
1810
+
1811
+ // ==================== Data Persistence ====================
1812
+
1813
+ saveConversations() {
1814
+ const data = Array.from(this.state.conversations.entries());
1815
+ localStorage.setItem('rox_ai_conversations', JSON.stringify(data));
1816
+ localStorage.setItem('rox_ai_current', this.state.currentConversationId || '');
1817
+ }
1818
+
1819
+ loadConversations() {
1820
+ try {
1821
+ const saved = localStorage.getItem('rox_ai_conversations');
1822
+ const current = localStorage.getItem('rox_ai_current');
1823
+
1824
+ if (saved) {
1825
+ const parsed = JSON.parse(saved);
1826
+ this.state.conversations = new Map(parsed);
1827
+ }
1828
+
1829
+ if (current && this.state.conversations.has(current)) {
1830
+ this.loadConversation(current);
1831
+ }
1832
+ } catch (e) {
1833
+ console.error('Failed to load conversations:', e);
1834
+ }
1835
+ }
1836
+
1837
+ renderConversationList() {
1838
+ const list = this.elements.conversationList;
1839
+ list.innerHTML = '';
1840
+
1841
+ const sorted = Array.from(this.state.conversations.values())
1842
+ .sort((a, b) => b.updatedAt - a.updatedAt)
1843
+ .slice(0, 10); // Show last 10
1844
+
1845
+ sorted.forEach(conv => {
1846
+ const item = document.createElement('div');
1847
+ item.className = `conversation-item ${conv.id === this.state.currentConversationId ? 'active' : ''}`;
1848
+ item.innerHTML = `
1849
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1850
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1851
+ </svg>
1852
+ <span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${conv.title}</span>
1853
+ `;
1854
+ item.onclick = () => this.loadConversation(conv.id);
1855
+ list.appendChild(item);
1856
+ });
1857
+ }
1858
+
1859
+ generateTitle(messages) {
1860
+ if (messages.length === 0) return 'New Conversation';
1861
+ const firstUser = messages.find(m => m.role === 'user');
1862
+ if (!firstUser) return 'New Conversation';
1863
+ return firstUser.content.slice(0, 30) + (firstUser.content.length > 30 ? '...' : '');
1864
+ }
1865
+
1866
+ // ==================== Utilities ====================
1867
+
1868
+ copyCode(button) {
1869
+ const code = button.closest('.code-block').querySelector('pre').textContent;
1870
+ navigator.clipboard.writeText(code).then(() => {
1871
+ button.textContent = 'Copied!';
1872
+ setTimeout(() => button.textContent = 'Copy', 2000);
1873
+ });
1874
+ }
1875
+
1876
+ showToast(message, type = 'info') {
1877
+ const toast = document.createElement('div');
1878
+ toast.className = `toast ${type}`;
1879
+
1880
+ const icons = {
1881
+ success: '✓',
1882
+ error: '✕',
1883
+ info: 'ℹ'
1884
+ };
1885
+
1886
+ toast.innerHTML = `<span>${icons[type]}</span> ${message}`;
1887
+ this.elements.toastContainer.appendChild(toast);
1888
+
1889
+ setTimeout(() => {
1890
+ toast.style.animation = 'toast-in 0.3s reverse forwards';
1891
+ setTimeout(() => toast.remove(), 300);
1892
+ }, 3000);
1893
+ }
1894
+
1895
+ handleError(error) {
1896
+ console.error('Chat error:', error);
1897
+ this.showToast(error.message || 'Failed to send message', 'error');
1898
+ this.addMessage('system', `Error: ${error.message}. Please try again.`);
1899
+ }
1900
+
1901
+ debounce(fn, ms) {
1902
+ let timeout;
1903
+ return (...args) => {
1904
+ clearTimeout(timeout);
1905
+ timeout = setTimeout(() => fn.apply(this, args), ms);
1906
+ };
1907
+ }
1908
+
1909
+ delay(ms) {
1910
+ return new Promise(resolve => setTimeout(resolve, ms));
1911
+ }
1912
+ }
1913
+
1914
+ // Initialize Application
1915
+ const chatApp = new ChatApplication();
1916
+ </script>
1917
+ </body>
1918
+ </html>