ibrahimlasfar commited on
Commit
355e9e7
·
1 Parent(s): 25c8cd4

Fix footer position, prompts size, flexible textarea, send button, prompts refresh, and add voice recording

Browse files
static/css/button.css CHANGED
@@ -3,7 +3,7 @@
3
  * SPDX-License-Identifier: Apache-2.0
4
  */
5
 
6
- #sendBtn, #stopBtn {
7
  width: 2.75rem;
8
  height: 2.75rem;
9
  border: none;
@@ -53,6 +53,39 @@
53
  transform: translateY(0) scale(0.98);
54
  }
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  #sendIcon {
57
  width: 1.25rem;
58
  height: 1.25rem;
@@ -62,4 +95,4 @@
62
 
63
  #sendBtn:hover:not(:disabled) #sendIcon {
64
  transform: translateX(2px);
65
- }
 
3
  * SPDX-License-Identifier: Apache-2.0
4
  */
5
 
6
+ #sendBtn, #stopBtn, #voiceBtn, #fileBtn, #audioBtn {
7
  width: 2.75rem;
8
  height: 2.75rem;
9
  border: none;
 
53
  transform: translateY(0) scale(0.98);
54
  }
55
 
56
+ #voiceBtn {
57
+ background: linear-gradient(135deg, #10b981, #059669);
58
+ color: white;
59
+ }
60
+
61
+ #voiceBtn.recording {
62
+ background: linear-gradient(135deg, #f87171, #ef4444);
63
+ transform: scale(1.1);
64
+ }
65
+
66
+ #voiceBtn:hover:not(.recording) {
67
+ transform: translateY(-2px) scale(1.05);
68
+ box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
69
+ }
70
+
71
+ #voiceBtn:active:not(.recording) {
72
+ transform: translateY(0) scale(0.98);
73
+ }
74
+
75
+ #fileBtn, #audioBtn {
76
+ background: linear-gradient(135deg, #6b7280, #4b5563);
77
+ color: white;
78
+ }
79
+
80
+ #fileBtn:hover, #audioBtn:hover {
81
+ transform: translateY(-2px) scale(1.05);
82
+ box-shadow: 0 4px 8px rgba(107, 114, 128, 0.3);
83
+ }
84
+
85
+ #fileBtn:active, #audioBtn:active {
86
+ transform: translateY(0) scale(0.98);
87
+ }
88
+
89
  #sendIcon {
90
  width: 1.25rem;
91
  height: 1.25rem;
 
95
 
96
  #sendBtn:hover:not(:disabled) #sendIcon {
97
  transform: translateX(2px);
98
+ }
static/css/footer.css CHANGED
@@ -6,16 +6,27 @@
6
  #footerForm {
7
  display: flex;
8
  gap: 0.75rem;
9
- background-color: transparent;
10
  align-items: center;
11
  width: 100%;
12
- max-width: 100%;
 
 
13
  box-sizing: border-box;
14
- position: fixed;
15
- bottom: 5rem;
16
- left: 0;
17
- right: 0;
18
  z-index: 1000;
19
- animation: slideInUp 0.3s ease-out forwards;
20
- height: 0;
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
6
  #footerForm {
7
  display: flex;
8
  gap: 0.75rem;
9
+ background: transparent;
10
  align-items: center;
11
  width: 100%;
12
+ max-width: 800px;
13
+ margin: 0 auto;
14
+ padding: 0.5rem 1rem;
15
  box-sizing: border-box;
16
+ position: sticky;
17
+ bottom: 0;
 
 
18
  z-index: 1000;
19
+ animation: fadeIn 0.3s ease-out forwards;
20
+ }
21
+
22
+ @keyframes fadeIn {
23
+ from { opacity: 0; transform: translateY(10px); }
24
+ to { opacity: 1; transform: translateY(0); }
25
+ }
26
+
27
+ @media (max-width: 768px) {
28
+ #footerForm {
29
+ max-width: 100%;
30
+ padding: 0.5rem;
31
+ }
32
+ }
static/css/input.css CHANGED
@@ -8,12 +8,14 @@
8
  align-items: center;
9
  flex: 1;
10
  background: linear-gradient(145deg, #1a1a1a, #141414);
11
- border-radius: 2rem;
12
  border: 1px solid rgba(255, 255, 255, 0.1);
13
  padding: 0.25rem;
14
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
15
- max-width: 100%;
 
16
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 
17
  }
18
 
19
  #inputContainer:focus-within {
@@ -25,8 +27,8 @@
25
 
26
  #userInput {
27
  flex: 1;
28
- padding: 0.75rem 0.75rem;
29
- border-radius: 2rem;
30
  border: 1px solid transparent;
31
  background-color: transparent;
32
  color: #e0e0e0;
@@ -35,7 +37,25 @@
35
  transition: border-color 0.2s, background-color 0.2s;
36
  word-break: break-word;
37
  overflow-wrap: break-word;
38
- max-width: 100%;
39
- min-width: 0;
 
40
  font-family: 'Inter', sans-serif;
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  align-items: center;
9
  flex: 1;
10
  background: linear-gradient(145deg, #1a1a1a, #141414);
11
+ border-radius: 1rem;
12
  border: 1px solid rgba(255, 255, 255, 0.1);
13
  padding: 0.25rem;
14
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
15
+ max-width: 800px;
16
+ margin: 0 auto;
17
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
18
+ position: relative;
19
  }
20
 
21
  #inputContainer:focus-within {
 
27
 
28
  #userInput {
29
  flex: 1;
30
+ padding: 0.75rem 3rem 0.75rem 0.75rem;
31
+ border-radius: 1rem;
32
  border: 1px solid transparent;
33
  background-color: transparent;
34
  color: #e0e0e0;
 
37
  transition: border-color 0.2s, background-color 0.2s;
38
  word-break: break-word;
39
  overflow-wrap: break-word;
40
+ min-height: 40px;
41
+ max-height: 120px;
42
+ resize: vertical;
43
  font-family: 'Inter', sans-serif;
44
+ }
45
+
46
+ #rightIconGroup {
47
+ position: absolute;
48
+ right: 0.5rem;
49
+ display: flex;
50
+ gap: 0.5rem;
51
+ }
52
+
53
+ @media (max-width: 768px) {
54
+ #inputContainer {
55
+ max-width: 100%;
56
+ padding: 0.25rem 0.5rem;
57
+ }
58
+ #userInput {
59
+ padding: 0.5rem 2.5rem 0.5rem 0.5rem;
60
+ }
61
+ }
static/css/prompts.css CHANGED
@@ -5,29 +5,31 @@
5
 
6
  .prompts {
7
  display: grid;
8
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
9
- gap: 1rem;
10
  width: 100%;
11
- max-width: 100%;
12
- margin-bottom: 1.5rem;
13
  }
14
 
15
  .prompt-item {
16
  background: linear-gradient(145deg, #1a1a1a, #141414);
17
- padding: 1rem 1.25rem;
18
- border-radius: var(--prompt-radius);
19
  display: flex;
20
  align-items: center;
21
- gap: 0.8rem;
22
  cursor: pointer;
23
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
24
  word-break: break-word;
25
  overflow-wrap: break-word;
26
  border: 1px solid rgba(255, 255, 255, 0.05);
27
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
28
  animation: fadeIn 0.5s ease-out forwards;
29
  transform: translateY(20px);
30
  opacity: 0;
 
 
31
  }
32
 
33
  .prompt-item:nth-child(1) { animation-delay: 0.1s; }
@@ -37,10 +39,23 @@
37
 
38
  .prompt-item:hover {
39
  background: linear-gradient(145deg, #1f1f1f, #1a1a1a);
40
- transform: translateY(-3px) scale(1.02);
41
- box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
42
  }
43
 
44
  .prompt-item:active {
45
  transform: translateY(0) scale(0.98);
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  .prompts {
7
  display: grid;
8
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
9
+ gap: 0.75rem;
10
  width: 100%;
11
+ max-width: 600px;
12
+ margin: 0 auto 2rem auto;
13
  }
14
 
15
  .prompt-item {
16
  background: linear-gradient(145deg, #1a1a1a, #141414);
17
+ padding: 0.5rem 0.75rem;
18
+ border-radius: 8px;
19
  display: flex;
20
  align-items: center;
21
+ gap: 0.5rem;
22
  cursor: pointer;
23
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
24
  word-break: break-word;
25
  overflow-wrap: break-word;
26
  border: 1px solid rgba(255, 255, 255, 0.05);
27
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
28
  animation: fadeIn 0.5s ease-out forwards;
29
  transform: translateY(20px);
30
  opacity: 0;
31
+ font-size: 0.9rem;
32
+ color: #fff;
33
  }
34
 
35
  .prompt-item:nth-child(1) { animation-delay: 0.1s; }
 
39
 
40
  .prompt-item:hover {
41
  background: linear-gradient(145deg, #1f1f1f, #1a1a1a);
42
+ transform: translateY(-2px) scale(1.02);
43
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
44
  }
45
 
46
  .prompt-item:active {
47
  transform: translateY(0) scale(0.98);
48
+ }
49
+
50
+ .prompt-item .icon {
51
+ width: 20px;
52
+ height: 20px;
53
+ margin-right: 0.5rem;
54
+ }
55
+
56
+ @media (max-width: 768px) {
57
+ .prompts {
58
+ grid-template-columns: 1fr;
59
+ max-width: 100%;
60
+ }
61
+ }
static/css/styles.css CHANGED
@@ -1,241 +1,259 @@
 
 
 
 
 
1
  body {
2
- font-family: 'Arial', sans-serif;
3
- margin: 0;
4
- padding: 0;
5
- background: linear-gradient(45deg, #1e3c72, #2a5298, #ff6f61, #1e3c72);
6
- background-size: 400% 400%;
7
- animation: gradient 15s ease infinite;
8
- color: #fff;
9
- min-height: 100vh;
10
- display: flex;
 
11
  }
12
 
13
  @keyframes gradient {
14
- 0% { background-position: 0% 50%; }
15
- 50% { background-position: 100% 50%; }
16
- 100% { background-position: 0% 50%; }
17
  }
18
 
19
  .sidebar {
20
- width: 250px;
21
- background: rgba(0, 0, 0, 0.8);
22
- padding: 20px;
23
- height: 100vh;
24
- position: fixed;
25
- top: 0;
26
- left: 0;
27
- display: flex;
28
- flex-direction: column;
29
- align-items: center;
30
  }
31
 
32
  .sidebar-logo {
33
- width: 80px;
34
- height: 80px;
35
- margin-bottom: 20px;
36
- filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
37
  }
38
 
39
  .sidebar nav {
40
- display: flex;
41
- flex-direction: column;
42
- gap: 10px;
43
  }
44
 
45
  .sidebar nav a {
46
- color: #fff;
47
- text-decoration: none;
48
- padding: 10px;
49
- border-radius: 5px;
50
- transition: background 0.3s;
51
  }
52
 
53
  .sidebar nav a:hover, .sidebar nav a.active {
54
- background: #ff6f61;
55
  }
56
 
57
  .container {
58
- max-width: 1200px;
59
- margin-left: 270px;
60
- padding: 20px;
61
- flex: 1;
62
  }
63
 
64
  .logo {
65
- width: 120px;
66
- height: 120px;
67
- margin-bottom: 20px;
68
- filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
69
  }
70
 
71
  h1 {
72
- font-size: 3.5rem;
73
- margin-bottom: 20px;
74
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
75
- animation: fadeIn 1s ease-in-out;
76
  }
77
 
78
  p {
79
- font-size: 1.2rem;
80
- line-height: 1.6;
81
- margin-bottom: 30px;
82
  }
83
 
84
  .btn {
85
- display: inline-block;
86
- padding: 15px 30px;
87
- background: linear-gradient(45deg, #ff6f61, #e55a50);
88
- color: #fff;
89
- text-decoration: none;
90
- border-radius: 25px;
91
- font-size: 1.1rem;
92
- transition: transform 0.3s, box-shadow 0.3s;
93
  }
94
 
95
  .btn:hover {
96
- transform: scale(1.1);
97
- box-shadow: 0 4px 15px rgba(255, 111, 97, 0.5);
98
  }
99
 
100
  .features, .docs, .news {
101
- margin-top: 40px;
102
- text-align: left;
103
  }
104
 
105
  .features h2, .docs h2, .news h2 {
106
- font-size: 2rem;
107
- margin-bottom: 20px;
108
- text-align: center;
109
- color: #fff;
110
  }
111
 
112
  .feature-grid, .footer-grid, .news-grid {
113
- display: grid;
114
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
115
- gap: 20px;
116
  }
117
 
118
  .feature-card, .footer-card, .news-card {
119
- background: rgba(255, 255, 255, 0.1);
120
- padding: 20px;
121
- border-radius: 10px;
122
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
123
- transition: transform 0.3s, box-shadow 0.3s, background 0.3s;
124
- text-align: center;
125
- aspect-ratio: 1/1;
126
  }
127
 
128
  .feature-card:hover, .footer-card:hover, .news-card:hover {
129
- transform: scale(1.05);
130
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
131
- background: rgba(255, 255, 255, 0.2);
132
  }
133
 
134
  .feature-card i, .footer-card i {
135
- font-size: 2.5rem;
136
- margin-bottom: 10px;
137
- color: #ff6f61;
138
  }
139
 
140
  .news-card a {
141
- color: #ff6f61;
142
- text-decoration: none;
143
- font-weight: bold;
144
  }
145
 
146
  .news-card a:hover {
147
- text-decoration: underline;
148
  }
149
 
150
  .code-block {
151
- position: relative;
152
- margin-bottom: 20px;
153
- background: #1a1a1a;
154
- padding: 20px;
155
- border-radius: 10px;
156
  }
157
 
158
  .code-block h3 {
159
- color: #ff6f61;
160
- margin-bottom: 10px;
161
  }
162
 
163
  .code-block pre {
164
- background: transparent;
165
- padding: 0;
166
- color: #c9e4ca;
167
- font-family: 'Courier New', monospace;
168
- overflow-x: auto;
169
  }
170
 
171
  .copy-btn {
172
- position: absolute;
173
- top: 10px;
174
- right: 10px;
175
- padding: 5px 10px;
176
- background: #ff6f61;
177
- color: #fff;
178
- border: none;
179
- border-radius: 5px;
180
- cursor: pointer;
181
- transition: background 0.3s;
182
  }
183
 
184
  .copy-btn:hover {
185
- background: #e55a50;
 
 
 
 
186
  }
187
 
188
  footer {
189
- background: linear-gradient(270deg, #1f2937, #111827, #1f2937);
190
- background-size: 200% 200%;
191
- animation: gradient 15s ease infinite;
192
- padding: 40px 20px;
193
- margin-top: 40px;
 
 
 
 
 
 
 
 
 
194
  }
195
 
196
- .footer-container {
197
- max-width: 1200px;
198
- margin: auto;
199
- text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
  .footer-logo {
203
- width: 100px;
204
- height: 100px;
205
- margin-bottom: 20px;
206
- filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
207
  }
208
 
209
  .footer-social a {
210
- font-size: 1.8rem;
211
- color: #fff;
212
- margin: 0 15px;
213
- transition: color 0.3s, transform 0.3s;
214
  }
215
 
216
  .footer-social a:hover {
217
- color: #ff6f61;
218
- transform: scale(1.2);
219
- }
220
-
221
- @keyframes fadeIn {
222
- from { opacity: 0; transform: translateY(20px); }
223
- to { opacity: 1; transform: translateY(0); }
224
  }
225
 
226
- @media (max-width: 768px) {
227
- .sidebar {
228
- width: 100%;
229
- height: auto;
230
- position: static;
231
- padding: 10px;
232
- }
233
- .container {
234
- margin-left: 0;
235
- }
236
- }
237
-
238
- /* /static/css/style.css */
239
  .chat-title {
240
  font-weight: 700;
241
  font-size: 1.2rem;
@@ -255,7 +273,7 @@ footer {
255
  align-items: center;
256
  gap: 0.5rem;
257
  padding: 10px;
258
- background: #ffffff;
259
  border-radius: 50px;
260
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
261
  }
@@ -264,11 +282,9 @@ footer {
264
  display: flex;
265
  flex-direction: column;
266
  width: 100%;
267
- height: 70vh;
268
  max-width: 800px;
269
  margin: 0 auto;
270
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
271
- background: #f5f7fa;
272
  }
273
 
274
  #chatArea {
@@ -278,25 +294,18 @@ footer {
278
  overflow-y: auto;
279
  width: 100%;
280
  box-sizing: border-box;
281
- overflow-wrap: break-word;
282
- word-wrap: break-word;
283
- word-break: break-word;
284
- padding: 20px;
285
- background: #ffffff;
286
- border-radius: 12px;
287
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
288
  }
289
 
290
  #chatBox {
291
  display: flex;
292
  flex-direction: column;
293
  width: 100%;
294
- overflow-y: auto;
295
- padding: 0.75rem 0.75rem calc(var(--footer-height) + 1rem) 1rem;
 
296
  box-sizing: border-box;
297
- overflow-wrap: break-word;
298
- word-wrap: break-word;
299
- word-break: break-word;
300
  }
301
 
302
  .bubble {
@@ -304,6 +313,7 @@ footer {
304
  padding: 10px 15px;
305
  border-radius: 8px;
306
  max-width: 80%;
 
307
  }
308
 
309
  .bubble-user {
@@ -314,55 +324,10 @@ footer {
314
  }
315
 
316
  .bubble-assist {
317
- background: #f1f0f0;
318
  margin-right: 20%;
319
  border-radius: 8px 8px 8px 0;
320
- }
321
-
322
- #inputContainer {
323
- display: flex;
324
- align-items: center;
325
- gap: 10px;
326
- background: #ffffff;
327
- border: 1px solid #e0e0e0;
328
- border-radius: 50px;
329
- padding: 10px;
330
- margin: 20px 0;
331
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
332
- }
333
-
334
- #userInput {
335
- flex-grow: 1;
336
- border: none;
337
- outline: none;
338
- font-size: 16px;
339
- padding: 12px 15px;
340
- background: transparent;
341
- }
342
-
343
- #rightIconGroup button {
344
- background: none;
345
- border: none;
346
- cursor: pointer;
347
- font-size: 20px;
348
- padding: 10px;
349
- color: #333;
350
- transition: color 0.2s;
351
- }
352
-
353
- #rightIconGroup button:hover {
354
- color: #25D366;
355
- }
356
-
357
- #sendBtn {
358
- background: #25D366;
359
- color: white;
360
- border-radius: 50%;
361
- width: 40px;
362
- height: 40px;
363
- display: flex;
364
- align-items: center;
365
- justify-content: center;
366
  }
367
 
368
  .upload-preview {
@@ -403,7 +368,7 @@ footer {
403
  }
404
 
405
  .md-content th {
406
- background: #f1f0f0;
407
  }
408
 
409
  .styled-hr {
@@ -412,3 +377,21 @@ footer {
412
  background: #ddd;
413
  margin: 20px 0;
414
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
  body {
7
+ font-family: 'Inter', sans-serif;
8
+ margin: 0;
9
+ padding: 0;
10
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
11
+ background-size: 400% 400%;
12
+ animation: gradient 15s ease infinite;
13
+ color: #fff;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
  }
18
 
19
  @keyframes gradient {
20
+ 0% { background-position: 0% 50%; }
21
+ 50% { background-position: 100% 50%; }
22
+ 100% { background-position: 0% 50%; }
23
  }
24
 
25
  .sidebar {
26
+ width: 250px;
27
+ background: rgba(0, 0, 0, 0.8);
28
+ padding: 20px;
29
+ height: 100vh;
30
+ position: fixed;
31
+ top: 0;
32
+ left: 0;
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
  }
37
 
38
  .sidebar-logo {
39
+ width: 80px;
40
+ height: 80px;
41
+ margin-bottom: 20px;
42
+ filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
43
  }
44
 
45
  .sidebar nav {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 10px;
49
  }
50
 
51
  .sidebar nav a {
52
+ color: #fff;
53
+ text-decoration: none;
54
+ padding: 10px;
55
+ border-radius: 5px;
56
+ transition: background 0.3s;
57
  }
58
 
59
  .sidebar nav a:hover, .sidebar nav a.active {
60
+ background: #ff6f61;
61
  }
62
 
63
  .container {
64
+ max-width: 1200px;
65
+ margin-left: 270px;
66
+ padding: 20px;
67
+ flex: 1;
68
  }
69
 
70
  .logo {
71
+ width: 120px;
72
+ height: 120px;
73
+ margin-bottom: 20px;
74
+ filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
75
  }
76
 
77
  h1 {
78
+ font-size: 3.5rem;
79
+ margin-bottom: 20px;
80
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
81
+ animation: fadeIn 1s ease-in-out;
82
  }
83
 
84
  p {
85
+ font-size: 1.2rem;
86
+ line-height: 1.6;
87
+ margin-bottom: 30px;
88
  }
89
 
90
  .btn {
91
+ display: inline-block;
92
+ padding: 15px 30px;
93
+ background: linear-gradient(45deg, #ff6f61, #e55a50);
94
+ color: #fff;
95
+ text-decoration: none;
96
+ border-radius: 25px;
97
+ font-size: 1.1rem;
98
+ transition: transform 0.3s, box-shadow 0.3s;
99
  }
100
 
101
  .btn:hover {
102
+ transform: scale(1.1);
103
+ box-shadow: 0 4px 15px rgba(255, 111, 97, 0.5);
104
  }
105
 
106
  .features, .docs, .news {
107
+ margin-top: 40px;
108
+ text-align: left;
109
  }
110
 
111
  .features h2, .docs h2, .news h2 {
112
+ font-size: 2rem;
113
+ margin-bottom: 20px;
114
+ text-align: center;
115
+ color: #fff;
116
  }
117
 
118
  .feature-grid, .footer-grid, .news-grid {
119
+ display: grid;
120
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
121
+ gap: 20px;
122
  }
123
 
124
  .feature-card, .footer-card, .news-card {
125
+ background: rgba(255, 255, 255, 0.1);
126
+ padding: 20px;
127
+ border-radius: 10px;
128
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
129
+ transition: transform 0.3s, box-shadow 0.3s, background 0.3s;
130
+ text-align: center;
131
+ aspect-ratio: 1/1;
132
  }
133
 
134
  .feature-card:hover, .footer-card:hover, .news-card:hover {
135
+ transform: scale(1.05);
136
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
137
+ background: rgba(255, 255, 255, 0.2);
138
  }
139
 
140
  .feature-card i, .footer-card i {
141
+ font-size: 2.5rem;
142
+ margin-bottom: 10px;
143
+ color: #ff6f61;
144
  }
145
 
146
  .news-card a {
147
+ color: #ff6f61;
148
+ text-decoration: none;
149
+ font-weight: bold;
150
  }
151
 
152
  .news-card a:hover {
153
+ text-decoration: underline;
154
  }
155
 
156
  .code-block {
157
+ position: relative;
158
+ margin-bottom: 20px;
159
+ background: #1a1a1a;
160
+ padding: 20px;
161
+ border-radius: 10px;
162
  }
163
 
164
  .code-block h3 {
165
+ color: #ff6f61;
166
+ margin-bottom: 10px;
167
  }
168
 
169
  .code-block pre {
170
+ background: transparent;
171
+ padding: 0;
172
+ color: #c9e4ca;
173
+ font-family: 'Courier New', monospace;
174
+ overflow-x: auto;
175
  }
176
 
177
  .copy-btn {
178
+ position: absolute;
179
+ top: 10px;
180
+ right: 10px;
181
+ padding: 5px 10px;
182
+ background: #ff6f61;
183
+ color: #fff;
184
+ border: none;
185
+ border-radius: 5px;
186
+ cursor: pointer;
187
+ transition: background 0.3s;
188
  }
189
 
190
  .copy-btn:hover {
191
+ background: #e55a50;
192
+ }
193
+
194
+ main {
195
+ flex: 1 0 auto;
196
  }
197
 
198
  footer {
199
+ flex-shrink: 0;
200
+ position: sticky;
201
+ bottom: 0;
202
+ width: 100%;
203
+ padding: 1rem 0;
204
+ background: linear-gradient(270deg, #1f2937, #111827, #1f2937);
205
+ background-size: 200% 200%;
206
+ animation: gradient 15s ease infinite;
207
+ }
208
+
209
+ footer .container {
210
+ max-width: 1200px;
211
+ padding: 0 1rem;
212
+ margin: 0 auto;
213
  }
214
 
215
+ footer .grid {
216
+ gap: 1rem;
217
+ }
218
+
219
+ footer .glass {
220
+ padding: 0.75rem;
221
+ background: rgba(255, 255, 255, 0.1);
222
+ backdrop-filter: blur(10px);
223
+ border-radius: 8px;
224
+ }
225
+
226
+ footer .glass h4 {
227
+ font-size: 1rem;
228
+ }
229
+
230
+ footer .glass p {
231
+ font-size: 0.875rem;
232
+ }
233
+
234
+ footer .glass .bx {
235
+ font-size: 1.5rem;
236
  }
237
 
238
  .footer-logo {
239
+ width: 100px;
240
+ height: 100px;
241
+ margin-bottom: 20px;
242
+ filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
243
  }
244
 
245
  .footer-social a {
246
+ font-size: 1.8rem;
247
+ color: #fff;
248
+ margin: 0 15px;
249
+ transition: color 0.3s, transform 0.3s;
250
  }
251
 
252
  .footer-social a:hover {
253
+ color: #ff6f61;
254
+ transform: scale(1.2);
 
 
 
 
 
255
  }
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  .chat-title {
258
  font-weight: 700;
259
  font-size: 1.2rem;
 
273
  align-items: center;
274
  gap: 0.5rem;
275
  padding: 10px;
276
+ background: rgba(255, 255, 255, 0.1);
277
  border-radius: 50px;
278
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
279
  }
 
282
  display: flex;
283
  flex-direction: column;
284
  width: 100%;
 
285
  max-width: 800px;
286
  margin: 0 auto;
287
+ font-family: 'Inter', sans-serif;
 
288
  }
289
 
290
  #chatArea {
 
294
  overflow-y: auto;
295
  width: 100%;
296
  box-sizing: border-box;
297
+ padding: 1rem;
298
+ background: transparent;
 
 
 
 
 
299
  }
300
 
301
  #chatBox {
302
  display: flex;
303
  flex-direction: column;
304
  width: 100%;
305
+ max-width: 800px;
306
+ margin: 0 auto;
307
+ padding: 0.75rem;
308
  box-sizing: border-box;
 
 
 
309
  }
310
 
311
  .bubble {
 
313
  padding: 10px 15px;
314
  border-radius: 8px;
315
  max-width: 80%;
316
+ word-break: break-word;
317
  }
318
 
319
  .bubble-user {
 
324
  }
325
 
326
  .bubble-assist {
327
+ background: rgba(255, 255, 255, 0.1);
328
  margin-right: 20%;
329
  border-radius: 8px 8px 8px 0;
330
+ color: #fff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  }
332
 
333
  .upload-preview {
 
368
  }
369
 
370
  .md-content th {
371
+ background: rgba(255, 255, 255, 0.1);
372
  }
373
 
374
  .styled-hr {
 
377
  background: #ddd;
378
  margin: 20px 0;
379
  }
380
+
381
+ @media (max-width: 768px) {
382
+ .sidebar {
383
+ width: 100%;
384
+ height: auto;
385
+ position: static;
386
+ padding: 10px;
387
+ }
388
+ .container {
389
+ margin-left: 0;
390
+ }
391
+ #chatBox {
392
+ max-width: 100%;
393
+ }
394
+ .bubble-user, .bubble-assist {
395
+ max-width: 90%;
396
+ }
397
+ }
static/js/chat.js CHANGED
@@ -15,6 +15,7 @@ const btn = document.getElementById('sendBtn');
15
  const stopBtn = document.getElementById('stopBtn');
16
  const fileBtn = document.getElementById('fileBtn');
17
  const audioBtn = document.getElementById('audioBtn');
 
18
  const fileInput = document.getElementById('fileInput');
19
  const audioInput = document.getElementById('audioInput');
20
  const filePreview = document.getElementById('filePreview');
@@ -30,360 +31,494 @@ const messageLimitWarning = document.getElementById('messageLimitWarning');
30
  // Track state.
31
  let streamMsg = null;
32
  let conversationHistory = JSON.parse(sessionStorage.getItem('conversationHistory') || '[]');
33
- let currentAssistantText = "";
34
  let isRequestActive = false;
35
  let abortController = null;
 
 
36
 
37
  // تحميل المحادثة عند تحميل الصفحة
38
  document.addEventListener('DOMContentLoaded', () => {
39
- AOS.init({
40
- duration: 800,
41
- easing: 'ease-out-cubic',
42
- once: true,
43
- offset: 50
 
 
 
 
 
44
  });
45
- if (conversationHistory.length > 0) {
46
- enterChatView();
47
- conversationHistory.forEach(msg => {
48
- addMsg(msg.role, msg.content);
49
- });
50
- }
51
  });
52
 
53
  // تحقق من الـ token
54
  function checkAuth() {
55
- return localStorage.getItem('token');
56
  }
57
 
58
  // Render markdown content.
59
  function renderMarkdown(el) {
60
- const raw = el.dataset.text || "";
61
- const html = marked.parse(raw, {
62
- gfm: true,
63
- breaks: true,
64
- smartLists: true,
65
- smartypants: false,
66
- headerIds: false
67
- });
68
- el.innerHTML = '<div class="md-content">' + html + '</div>';
69
- const wrapper = el.querySelector('.md-content');
70
-
71
- // Wrap tables.
72
- const tables = wrapper.querySelectorAll('table');
73
- tables.forEach(t => {
74
- if (t.parentNode && t.parentNode.classList && t.parentNode.classList.contains('table-wrapper')) return;
75
- const div = document.createElement('div');
76
- div.className = 'table-wrapper';
77
- t.parentNode.insertBefore(div, t);
78
- div.appendChild(t);
79
- });
80
-
81
- // Style horizontal rules.
82
- const hrs = wrapper.querySelectorAll('hr');
83
- hrs.forEach(h => {
84
- if (!h.classList.contains('styled-hr')) {
85
- h.classList.add('styled-hr');
86
- }
87
- });
88
 
89
- // Highlight code.
90
- Prism.highlightAllUnder(wrapper);
91
  }
92
 
93
  // Chat view.
94
  function enterChatView() {
95
- mainHeader.style.display = 'none';
96
- chatHeader.style.display = 'flex';
97
- chatHeader.setAttribute('aria-hidden', 'false');
98
- chatBox.style.display = 'flex';
99
- initialContent.style.display = 'none';
100
  }
101
 
102
  // Home view.
103
  function leaveChatView() {
104
- mainHeader.style.display = 'flex';
105
- chatHeader.style.display = 'none';
106
- chatHeader.setAttribute('aria-hidden', 'true');
107
- chatBox.style.display = 'none';
108
- initialContent.style.display = 'flex';
109
  }
110
 
111
  // Chat bubble.
112
  function addMsg(who, text) {
113
- const div = document.createElement('div');
114
- div.className = 'bubble ' + (who === 'user' ? 'bubble-user' : 'bubble-assist');
115
- div.dataset.text = text;
116
- renderMarkdown(div);
117
- chatBox.appendChild(div);
118
- chatBox.style.display = 'flex';
119
- chatBox.scrollTop = chatBox.scrollHeight;
120
- return div;
121
  }
122
 
123
  // Clear all chat.
124
  function clearAllMessages() {
125
- stopStream(true);
126
- conversationHistory = [];
127
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
128
- currentAssistantText = "";
129
- if (streamMsg) {
130
- const loadingEl = streamMsg.querySelector('.loading');
131
- if (loadingEl) loadingEl.remove();
132
- streamMsg = null;
133
- }
134
- chatBox.innerHTML = "";
135
- input.value = "";
136
- btn.disabled = true;
137
- stopBtn.style.display = 'none';
138
- btn.style.display = 'inline-flex';
139
- filePreview.style.display = 'none';
140
- audioPreview.style.display = 'none';
141
- messageLimitWarning.classList.add('hidden');
142
- enterChatView();
143
  }
144
 
145
  // File preview.
146
  function previewFile() {
147
- if (fileInput.files.length > 0) {
148
- const file = fileInput.files[0];
149
- if (file.type.startsWith('image/')) {
150
- const reader = new FileReader();
151
- reader.onload = (e) => {
152
- filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
153
- filePreview.style.display = 'block';
154
- audioPreview.style.display = 'none';
155
- };
156
- reader.readAsDataURL(file);
157
- }
158
  }
159
- if (audioInput.files.length > 0) {
160
- const file = audioInput.files[0];
161
- if (file.type.startsWith('audio/')) {
162
- const reader = new FileReader();
163
- reader.onload = (e) => {
164
- audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
165
- audioPreview.style.display = 'block';
166
- filePreview.style.display = 'none';
167
- };
168
- reader.readAsDataURL(file);
169
- }
170
  }
 
171
  }
172
 
173
- // Send user message.
174
- async function submitMessage() {
175
- if (isRequestActive) return;
176
- let message = input.value.trim();
177
- let formData = new FormData();
178
- let endpoint = '/api/chat';
179
- let inputType = 'text';
180
- let outputFormat = 'text';
181
-
182
- if (fileInput.files.length > 0) {
183
- const file = fileInput.files[0];
184
- if (file.type.startsWith('image/')) {
185
- endpoint = '/api/image-analysis';
186
- inputType = 'image';
187
- message = 'Analyze this image';
188
- formData.append('file', file);
189
- formData.append('output_format', 'text');
190
- }
191
- } else if (audioInput.files.length > 0) {
192
- const file = audioInput.files[0];
193
- if (file.type.startsWith('audio/')) {
194
- endpoint = '/api/audio-transcription';
195
- inputType = 'audio';
196
- message = 'Transcribe this audio';
197
- formData.append('file', file);
198
- }
199
- } else if (message) {
200
- formData.append('message', message);
201
- formData.append('system_prompt', 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.');
202
- formData.append('history', JSON.stringify(conversationHistory));
203
- formData.append('temperature', '0.7');
204
- formData.append('max_new_tokens', '128000');
205
- formData.append('enable_browsing', 'true');
206
- formData.append('output_format', 'text');
207
- } else {
208
- return;
209
- }
210
 
211
- enterChatView();
212
- addMsg('user', message);
213
- conversationHistory.push({ role: 'user', content: message });
214
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
215
- streamMsg = addMsg('assistant', '');
216
- const loadingEl = document.createElement('span');
217
- loadingEl.className = 'loading';
218
- streamMsg.appendChild(loadingEl);
219
- stopBtn.style.display = 'inline-flex';
220
- btn.style.display = 'none';
221
- input.value = '';
222
- btn.disabled = true;
223
- filePreview.style.display = 'none';
224
- audioPreview.style.display = 'none';
225
-
226
- isRequestActive = true;
227
- abortController = new AbortController();
228
-
229
- try {
230
- const token = checkAuth();
231
- const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
232
- const response = await fetch(endpoint, {
233
- method: 'POST',
234
- body: formData,
235
- headers: headers,
236
- signal: abortController.signal
237
- });
238
-
239
- if (!response.ok) {
240
- if (response.status === 403) {
241
- messageLimitWarning.classList.remove('hidden');
242
- input.disabled = true;
243
- const loadingEl = streamMsg.querySelector('.loading');
244
- if (loadingEl) loadingEl.remove();
245
- streamMsg = null;
246
- isRequestActive = false;
247
- abortController = null;
248
- btn.style.display = 'inline-flex';
249
- stopBtn.style.display = 'none';
250
- return;
251
- }
252
- if (response.status === 401) {
253
- localStorage.removeItem('token');
254
- window.location.href = '/login';
255
- return;
256
- }
257
- throw new Error('Request failed');
258
- }
259
-
260
- if (endpoint === '/api/audio-transcription') {
261
- const data = await response.json();
262
- const transcription = data.transcription || 'Error: No transcription generated.';
263
- streamMsg.dataset.text = transcription;
264
- renderMarkdown(streamMsg);
265
- streamMsg.dataset.done = '1';
266
- conversationHistory.push({ role: 'assistant', content: transcription });
267
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
268
- } else if (endpoint === '/api/image-analysis') {
269
- const data = await response.json();
270
- const analysis = data.image_analysis || 'Error: No analysis generated.';
271
- streamMsg.dataset.text = analysis;
272
- renderMarkdown(streamMsg);
273
- streamMsg.dataset.done = '1';
274
- conversationHistory.push({ role: 'assistant', content: analysis });
275
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
276
- } else {
277
- const reader = response.body.getReader();
278
- const decoder = new TextDecoder();
279
- let buffer = '';
280
-
281
- while (true) {
282
- const { done, value } = await reader.read();
283
- if (done) break;
284
- buffer += decoder.decode(value, { stream: true });
285
- streamMsg.dataset.text = buffer;
286
- currentAssistantText = buffer;
287
- const loadingEl = streamMsg.querySelector('.loading');
288
- if (loadingEl) loadingEl.remove();
289
- renderMarkdown(streamMsg);
290
- chatBox.scrollTop = chatBox.scrollHeight;
291
- }
292
- streamMsg.dataset.done = '1';
293
- conversationHistory.push({ role: 'assistant', content: buffer });
294
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
295
- }
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  streamMsg = null;
298
  isRequestActive = false;
299
  abortController = null;
300
  btn.style.display = 'inline-flex';
301
  stopBtn.style.display = 'none';
302
- } catch (error) {
303
- if (streamMsg) {
304
- const loadingEl = streamMsg.querySelector('.loading');
305
- if (loadingEl) loadingEl.remove();
306
- streamMsg.dataset.text = error.message || 'An error occurred during the request.';
307
- renderMarkdown(streamMsg);
308
- streamMsg.dataset.done = '1';
309
- streamMsg = null;
310
- isRequestActive = false;
311
- abortController = null;
312
- }
313
- btn.style.display = 'inline-flex';
314
- stopBtn.style.display = 'none';
315
  }
316
- }
317
 
318
- // Stop streaming and cancel the ongoing request.
319
- function stopStream(forceCancel = false) {
320
- if (!isRequestActive) return;
 
 
 
 
 
 
321
  isRequestActive = false;
322
- if (abortController) {
323
- abortController.abort();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  abortController = null;
 
 
 
 
 
 
 
 
 
 
 
325
  }
326
- if (streamMsg && !forceCancel) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  const loadingEl = streamMsg.querySelector('.loading');
328
  if (loadingEl) loadingEl.remove();
329
- streamMsg.dataset.text += '';
330
  renderMarkdown(streamMsg);
331
- streamMsg.dataset.done = '1';
332
- streamMsg = null;
 
 
 
333
  }
 
 
 
 
 
334
  stopBtn.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
335
  btn.style.display = 'inline-flex';
336
- stopBtn.style.pointerEvents = 'auto';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  }
338
 
339
  // Prompts.
340
  promptItems.forEach(p => {
341
- p.addEventListener('click', () => {
342
- input.value = p.dataset.prompt;
343
- submitMessage();
344
- });
 
345
  });
346
 
347
  // File and audio inputs.
348
  fileBtn.addEventListener('click', () => {
349
- fileInput.click();
350
  });
351
  audioBtn.addEventListener('click', () => {
352
- audioInput.click();
353
  });
354
  fileInput.addEventListener('change', previewFile);
355
  audioInput.addEventListener('change', previewFile);
356
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  // Submit.
358
  form.addEventListener('submit', e => {
 
 
 
 
 
 
 
359
  e.preventDefault();
360
  submitMessage();
 
361
  });
362
 
363
  // Stop.
364
  stopBtn.addEventListener('click', () => {
365
- stopBtn.style.pointerEvents = 'none';
366
- stopStream();
367
  });
368
 
369
  // Home.
370
  homeBtn.addEventListener('click', () => {
371
- window.location.href = '/';
372
  });
373
 
374
  // Clear messages.
375
  clearBtn.addEventListener('click', () => {
376
- clearAllMessages();
377
  });
378
 
379
  // Login button.
380
  if (loginBtn) {
381
- loginBtn.addEventListener('click', () => {
382
- window.location.href = '/login';
383
- });
384
  }
385
 
386
- // Enable send button only if input has text.
387
  input.addEventListener('input', () => {
388
- btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
389
  });
 
15
  const stopBtn = document.getElementById('stopBtn');
16
  const fileBtn = document.getElementById('fileBtn');
17
  const audioBtn = document.getElementById('audioBtn');
18
+ const voiceBtn = document.getElementById('voiceBtn');
19
  const fileInput = document.getElementById('fileInput');
20
  const audioInput = document.getElementById('audioInput');
21
  const filePreview = document.getElementById('filePreview');
 
31
  // Track state.
32
  let streamMsg = null;
33
  let conversationHistory = JSON.parse(sessionStorage.getItem('conversationHistory') || '[]');
34
+ let currentAssistantText = '';
35
  let isRequestActive = false;
36
  let abortController = null;
37
+ let mediaRecorder = null;
38
+ let audioChunks = [];
39
 
40
  // تحميل المحادثة عند تحميل الصفحة
41
  document.addEventListener('DOMContentLoaded', () => {
42
+ AOS.init({
43
+ duration: 800,
44
+ easing: 'ease-out-cubic',
45
+ once: true,
46
+ offset: 50,
47
+ });
48
+ if (conversationHistory.length > 0) {
49
+ enterChatView();
50
+ conversationHistory.forEach(msg => {
51
+ addMsg(msg.role, msg.content);
52
  });
53
+ }
 
 
 
 
 
54
  });
55
 
56
  // تحقق من الـ token
57
  function checkAuth() {
58
+ return localStorage.getItem('token');
59
  }
60
 
61
  // Render markdown content.
62
  function renderMarkdown(el) {
63
+ const raw = el.dataset.text || '';
64
+ const html = marked.parse(raw, {
65
+ gfm: true,
66
+ breaks: true,
67
+ smartLists: true,
68
+ smartypants: false,
69
+ headerIds: false,
70
+ });
71
+ el.innerHTML = '<div class="md-content">' + html + '</div>';
72
+ const wrapper = el.querySelector('.md-content');
73
+
74
+ // Wrap tables.
75
+ const tables = wrapper.querySelectorAll('table');
76
+ tables.forEach(t => {
77
+ if (t.parentNode && t.parentNode.classList && t.parentNode.classList.contains('table-wrapper')) return;
78
+ const div = document.createElement('div');
79
+ div.className = 'table-wrapper';
80
+ t.parentNode.insertBefore(div, t);
81
+ div.appendChild(t);
82
+ });
83
+
84
+ // Style horizontal rules.
85
+ const hrs = wrapper.querySelectorAll('hr');
86
+ hrs.forEach(h => {
87
+ if (!h.classList.contains('styled-hr')) {
88
+ h.classList.add('styled-hr');
89
+ }
90
+ });
91
 
92
+ // Highlight code.
93
+ Prism.highlightAllUnder(wrapper);
94
  }
95
 
96
  // Chat view.
97
  function enterChatView() {
98
+ mainHeader.style.display = 'none';
99
+ chatHeader.style.display = 'flex';
100
+ chatHeader.setAttribute('aria-hidden', 'false');
101
+ chatBox.style.display = 'flex';
102
+ initialContent.style.display = 'none';
103
  }
104
 
105
  // Home view.
106
  function leaveChatView() {
107
+ mainHeader.style.display = 'flex';
108
+ chatHeader.style.display = 'none';
109
+ chatHeader.setAttribute('aria-hidden', 'true');
110
+ chatBox.style.display = 'none';
111
+ initialContent.style.display = 'flex';
112
  }
113
 
114
  // Chat bubble.
115
  function addMsg(who, text) {
116
+ const div = document.createElement('div');
117
+ div.className = 'bubble ' + (who === 'user' ? 'bubble-user' : 'bubble-assist');
118
+ div.dataset.text = text;
119
+ renderMarkdown(div);
120
+ chatBox.appendChild(div);
121
+ chatBox.style.display = 'flex';
122
+ chatBox.scrollTop = chatBox.scrollHeight;
123
+ return div;
124
  }
125
 
126
  // Clear all chat.
127
  function clearAllMessages() {
128
+ stopStream(true);
129
+ conversationHistory = [];
130
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
131
+ currentAssistantText = '';
132
+ if (streamMsg) {
133
+ const loadingEl = streamMsg.querySelector('.loading');
134
+ if (loadingEl) loadingEl.remove();
135
+ streamMsg = null;
136
+ }
137
+ chatBox.innerHTML = '';
138
+ input.value = '';
139
+ btn.disabled = true;
140
+ stopBtn.style.display = 'none';
141
+ btn.style.display = 'inline-flex';
142
+ filePreview.style.display = 'none';
143
+ audioPreview.style.display = 'none';
144
+ messageLimitWarning.classList.add('hidden');
145
+ enterChatView();
146
  }
147
 
148
  // File preview.
149
  function previewFile() {
150
+ if (fileInput.files.length > 0) {
151
+ const file = fileInput.files[0];
152
+ if (file.type.startsWith('image/')) {
153
+ const reader = new FileReader();
154
+ reader.onload = e => {
155
+ filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
156
+ filePreview.style.display = 'block';
157
+ audioPreview.style.display = 'none';
158
+ };
159
+ reader.readAsDataURL(file);
 
160
  }
161
+ }
162
+ if (audioInput.files.length > 0) {
163
+ const file = audioInput.files[0];
164
+ if (file.type.startsWith('audio/')) {
165
+ const reader = new FileReader();
166
+ reader.onload = e => {
167
+ audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
168
+ audioPreview.style.display = 'block';
169
+ filePreview.style.display = 'none';
170
+ };
171
+ reader.readAsDataURL(file);
172
  }
173
+ }
174
  }
175
 
176
+ // Voice recording.
177
+ function startVoiceRecording() {
178
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
179
+ mediaRecorder = new MediaRecorder(stream);
180
+ audioChunks = [];
181
+ mediaRecorder.start();
182
+ voiceBtn.classList.add('recording');
183
+ mediaRecorder.addEventListener('dataavailable', event => {
184
+ audioChunks.push(event.data);
185
+ });
186
+ }).catch(err => {
187
+ console.error('Error accessing microphone:', err);
188
+ alert('Failed to access microphone. Please check permissions.');
189
+ });
190
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
+ function stopVoiceRecording() {
193
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
194
+ mediaRecorder.stop();
195
+ voiceBtn.classList.remove('recording');
196
+ mediaRecorder.addEventListener('stop', async () => {
197
+ const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
198
+ const formData = new FormData();
199
+ formData.append('file', audioBlob, 'voice-message.webm');
200
+ submitAudioMessage(formData);
201
+ });
202
+ }
203
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
+ // Send audio message.
206
+ async function submitAudioMessage(formData) {
207
+ enterChatView();
208
+ addMsg('user', 'Voice message');
209
+ conversationHistory.push({ role: 'user', content: 'Voice message' });
210
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
211
+ streamMsg = addMsg('assistant', '');
212
+ const loadingEl = document.createElement('span');
213
+ loadingEl.className = 'loading';
214
+ streamMsg.appendChild(loadingEl);
215
+ stopBtn.style.display = 'inline-flex';
216
+ btn.style.display = 'none';
217
+ input.value = '';
218
+ btn.disabled = true;
219
+ filePreview.style.display = 'none';
220
+ audioPreview.style.display = 'none';
221
+
222
+ isRequestActive = true;
223
+ abortController = new AbortController();
224
+
225
+ try {
226
+ const token = checkAuth();
227
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
228
+ const response = await fetch('/api/audio-transcription', {
229
+ method: 'POST',
230
+ body: formData,
231
+ headers: headers,
232
+ signal: abortController.signal,
233
+ });
234
+
235
+ if (!response.ok) {
236
+ if (response.status === 403) {
237
+ messageLimitWarning.classList.remove('hidden');
238
+ input.disabled = true;
239
+ const loadingEl = streamMsg.querySelector('.loading');
240
+ if (loadingEl) loadingEl.remove();
241
  streamMsg = null;
242
  isRequestActive = false;
243
  abortController = null;
244
  btn.style.display = 'inline-flex';
245
  stopBtn.style.display = 'none';
246
+ window.location.href = '/login';
247
+ return;
248
+ }
249
+ if (response.status === 401) {
250
+ localStorage.removeItem('token');
251
+ window.location.href = '/login';
252
+ return;
253
+ }
254
+ throw new Error('Request failed');
 
 
 
 
255
  }
 
256
 
257
+ const data = await response.json();
258
+ const transcription = data.transcription || 'Error: No transcription generated.';
259
+ streamMsg.dataset.text = transcription;
260
+ renderMarkdown(streamMsg);
261
+ streamMsg.dataset.done = '1';
262
+ conversationHistory.push({ role: 'assistant', content: transcription });
263
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
264
+
265
+ streamMsg = null;
266
  isRequestActive = false;
267
+ abortController = null;
268
+ btn.style.display = 'inline-flex';
269
+ stopBtn.style.display = 'none';
270
+ } catch (error) {
271
+ if (streamMsg) {
272
+ const loadingEl = streamMsg.querySelector('.loading');
273
+ if (loadingEl) loadingEl.remove();
274
+ streamMsg.dataset.text = error.message || 'An error occurred during the request.';
275
+ renderMarkdown(streamMsg);
276
+ streamMsg.dataset.done = '1';
277
+ streamMsg = null;
278
+ isRequestActive = false;
279
+ abortController = null;
280
+ }
281
+ btn.style.display = 'inline-flex';
282
+ stopBtn.style.display = 'none';
283
+ }
284
+ }
285
+
286
+ // Send user message.
287
+ async function submitMessage() {
288
+ if (isRequestActive) return;
289
+ let message = input.value.trim();
290
+ let formData = new FormData();
291
+ let endpoint = '/api/chat';
292
+ let inputType = 'text';
293
+ let outputFormat = 'text';
294
+
295
+ if (fileInput.files.length > 0) {
296
+ const file = fileInput.files[0];
297
+ if (file.type.startsWith('image/')) {
298
+ endpoint = '/api/image-analysis';
299
+ inputType = 'image';
300
+ message = 'Analyze this image';
301
+ formData.append('file', file);
302
+ formData.append('output_format', 'text');
303
+ }
304
+ } else if (audioInput.files.length > 0) {
305
+ const file = audioInput.files[0];
306
+ if (file.type.startsWith('audio/')) {
307
+ endpoint = '/api/audio-transcription';
308
+ inputType = 'audio';
309
+ message = 'Transcribe this audio';
310
+ formData.append('file', file);
311
+ }
312
+ } else if (message) {
313
+ formData.append('message', message);
314
+ formData.append('system_prompt', 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.');
315
+ formData.append('history', JSON.stringify(conversationHistory));
316
+ formData.append('temperature', '0.7');
317
+ formData.append('max_new_tokens', '128000');
318
+ formData.append('enable_browsing', 'true');
319
+ formData.append('output_format', 'text');
320
+ } else {
321
+ return;
322
+ }
323
+
324
+ enterChatView();
325
+ addMsg('user', message);
326
+ conversationHistory.push({ role: 'user', content: message });
327
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
328
+ streamMsg = addMsg('assistant', '');
329
+ const loadingEl = document.createElement('span');
330
+ loadingEl.className = 'loading';
331
+ streamMsg.appendChild(loadingEl);
332
+ stopBtn.style.display = 'inline-flex';
333
+ btn.style.display = 'none';
334
+ input.value = '';
335
+ btn.disabled = true;
336
+ filePreview.style.display = 'none';
337
+ audioPreview.style.display = 'none';
338
+
339
+ isRequestActive = true;
340
+ abortController = new AbortController();
341
+
342
+ try {
343
+ const token = checkAuth();
344
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
345
+ const response = await fetch(endpoint, {
346
+ method: 'POST',
347
+ body: formData,
348
+ headers: headers,
349
+ signal: abortController.signal,
350
+ });
351
+
352
+ if (!response.ok) {
353
+ if (response.status === 403) {
354
+ messageLimitWarning.classList.remove('hidden');
355
+ input.disabled = true;
356
+ const loadingEl = streamMsg.querySelector('.loading');
357
+ if (loadingEl) loadingEl.remove();
358
+ streamMsg = null;
359
+ isRequestActive = false;
360
  abortController = null;
361
+ btn.style.display = 'inline-flex';
362
+ stopBtn.style.display = 'none';
363
+ window.location.href = '/login';
364
+ return;
365
+ }
366
+ if (response.status === 401) {
367
+ localStorage.removeItem('token');
368
+ window.location.href = '/login';
369
+ return;
370
+ }
371
+ throw new Error('Request failed');
372
  }
373
+
374
+ if (endpoint === '/api/audio-transcription') {
375
+ const data = await response.json();
376
+ const transcription = data.transcription || 'Error: No transcription generated.';
377
+ streamMsg.dataset.text = transcription;
378
+ renderMarkdown(streamMsg);
379
+ streamMsg.dataset.done = '1';
380
+ conversationHistory.push({ role: 'assistant', content: transcription });
381
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
382
+ } else if (endpoint === '/api/image-analysis') {
383
+ const data = await response.json();
384
+ const analysis = data.image_analysis || 'Error: No analysis generated.';
385
+ streamMsg.dataset.text = analysis;
386
+ renderMarkdown(streamMsg);
387
+ streamMsg.dataset.done = '1';
388
+ conversationHistory.push({ role: 'assistant', content: analysis });
389
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
390
+ } else {
391
+ const reader = response.body.getReader();
392
+ const decoder = new TextDecoder();
393
+ let buffer = '';
394
+
395
+ while (true) {
396
+ const { done, value } = await reader.read();
397
+ if (done) break;
398
+ buffer += decoder.decode(value, { stream: true });
399
+ streamMsg.dataset.text = buffer;
400
+ currentAssistantText = buffer;
401
  const loadingEl = streamMsg.querySelector('.loading');
402
  if (loadingEl) loadingEl.remove();
 
403
  renderMarkdown(streamMsg);
404
+ chatBox.scrollTop = chatBox.scrollHeight;
405
+ }
406
+ streamMsg.dataset.done = '1';
407
+ conversationHistory.push({ role: 'assistant', content: buffer });
408
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
409
  }
410
+
411
+ streamMsg = null;
412
+ isRequestActive = false;
413
+ abortController = null;
414
+ btn.style.display = 'inline-flex';
415
  stopBtn.style.display = 'none';
416
+ } catch (error) {
417
+ if (streamMsg) {
418
+ const loadingEl = streamMsg.querySelector('.loading');
419
+ if (loadingEl) loadingEl.remove();
420
+ streamMsg.dataset.text = error.message || 'An error occurred during the request.';
421
+ renderMarkdown(streamMsg);
422
+ streamMsg.dataset.done = '1';
423
+ streamMsg = null;
424
+ isRequestActive = false;
425
+ abortController = null;
426
+ }
427
  btn.style.display = 'inline-flex';
428
+ stopBtn.style.display = 'none';
429
+ }
430
+ }
431
+
432
+ // Stop streaming and cancel the ongoing request.
433
+ function stopStream(forceCancel = false) {
434
+ if (!isRequestActive) return;
435
+ isRequestActive = false;
436
+ if (abortController) {
437
+ abortController.abort();
438
+ abortController = null;
439
+ }
440
+ if (streamMsg && !forceCancel) {
441
+ const loadingEl = streamMsg.querySelector('.loading');
442
+ if (loadingEl) loadingEl.remove();
443
+ streamMsg.dataset.text += '';
444
+ renderMarkdown(streamMsg);
445
+ streamMsg.dataset.done = '1';
446
+ streamMsg = null;
447
+ }
448
+ stopBtn.style.display = 'none';
449
+ btn.style.display = 'inline-flex';
450
+ stopBtn.style.pointerEvents = 'auto';
451
  }
452
 
453
  // Prompts.
454
  promptItems.forEach(p => {
455
+ p.addEventListener('click', e => {
456
+ e.preventDefault();
457
+ input.value = p.dataset.prompt;
458
+ submitMessage();
459
+ });
460
  });
461
 
462
  // File and audio inputs.
463
  fileBtn.addEventListener('click', () => {
464
+ fileInput.click();
465
  });
466
  audioBtn.addEventListener('click', () => {
467
+ audioInput.click();
468
  });
469
  fileInput.addEventListener('change', previewFile);
470
  audioInput.addEventListener('change', previewFile);
471
 
472
+ // Voice recording events.
473
+ voiceBtn.addEventListener('mousedown', startVoiceRecording);
474
+ voiceBtn.addEventListener('touchstart', e => {
475
+ e.preventDefault();
476
+ startVoiceRecording();
477
+ });
478
+ voiceBtn.addEventListener('mouseup', stopVoiceRecording);
479
+ voiceBtn.addEventListener('touchend', e => {
480
+ e.preventDefault();
481
+ stopVoiceRecording();
482
+ });
483
+
484
  // Submit.
485
  form.addEventListener('submit', e => {
486
+ e.preventDefault();
487
+ submitMessage();
488
+ });
489
+
490
+ // Handle Enter key to submit without adding new line.
491
+ input.addEventListener('keydown', e => {
492
+ if (e.key === 'Enter' && !e.shiftKey) {
493
  e.preventDefault();
494
  submitMessage();
495
+ }
496
  });
497
 
498
  // Stop.
499
  stopBtn.addEventListener('click', () => {
500
+ stopBtn.style.pointerEvents = 'none';
501
+ stopStream();
502
  });
503
 
504
  // Home.
505
  homeBtn.addEventListener('click', () => {
506
+ window.location.href = '/';
507
  });
508
 
509
  // Clear messages.
510
  clearBtn.addEventListener('click', () => {
511
+ clearAllMessages();
512
  });
513
 
514
  // Login button.
515
  if (loginBtn) {
516
+ loginBtn.addEventListener('click', () => {
517
+ window.location.href = '/login';
518
+ });
519
  }
520
 
521
+ // Enable send button only if input has text or files.
522
  input.addEventListener('input', () => {
523
+ btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
524
  });
templates/chat.html CHANGED
@@ -123,26 +123,26 @@
123
  </p>
124
  <!-- Prompts -->
125
  <div class="prompts w-full max-w-md mx-auto grid gap-2">
126
- <div class="prompt-item" data-prompt="What's the capital of France?" style="word-break: break-word;">
127
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
128
  <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M18.66 18.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M18.66 5.34l1.41-1.41" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
129
  <circle cx="12" cy="12" r="4" stroke-width="2" />
130
  </svg>
131
  <span>What's the capital of France?</span>
132
  </div>
133
- <div class="prompt-item" data-prompt="Generate a Python script for a simple web server" style="word-break: break-word;">
134
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
135
  <path d="M4 7h16M4 12h16M4 17h16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
136
  </svg>
137
  <span>Generate a Python script for a simple web server</span>
138
  </div>
139
- <div class="prompt-item" data-prompt="Analyze this image for me" style="word-break: break-word;">
140
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
141
  <path d="M3 12h18M12 3v18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
142
  </svg>
143
  <span>Analyze this image for me</span>
144
  </div>
145
- <div class="prompt-item" data-prompt="Convert this text to audio" style="word-break: break-word;">
146
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
147
  <path d="M12 3v18M7 12h10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
148
  </svg>
@@ -157,9 +157,9 @@
157
  <div id="chatBox" class="flex-col" aria-live="polite"></div>
158
  </div>
159
  <!-- Footer Form -->
160
- <form id="footerForm" class="flex p-3 bg-transparent" autocomplete="off" style="position: relative; margin-top: auto; width: 100%;">
161
  <div id="inputContainer" class="w-full">
162
- <input type="text" id="userInput" placeholder="Ask anything..." required style="word-break: break-word;" />
163
  <div id="rightIconGroup" class="flex gap-2">
164
  <button type="button" id="fileBtn" class="icon-btn" aria-label="Upload File" title="Upload File">
165
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
@@ -168,7 +168,7 @@
168
  </svg>
169
  </button>
170
  <input type="file" id="fileInput" accept="image/*,.mp3,.wav" style="display: none;" />
171
- <button type="button" id="audioBtn" class="icon-btn" aria-label="Record Audio" title="Record Audio">
172
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
173
  <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
174
  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
@@ -176,6 +176,13 @@
176
  </svg>
177
  </button>
178
  <input type="file" id="audioInput" accept="audio/*" style="display: none;" />
 
 
 
 
 
 
 
179
  <button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send" title="Send">
180
  <svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
181
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
@@ -194,48 +201,48 @@
194
  </main>
195
 
196
  <!-- Footer -->
197
- <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 w-full">
198
  <div class="container max-w-6xl mx-auto text-center">
199
- <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
200
- <p class="mb-4">
201
  Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
202
  | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
203
  </p>
204
- <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
205
- <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
206
- <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
207
- <h4 class="font-semibold mb-1">Email Us</h4>
208
- <p><a href="mailto:support@mgzon.com" class="text-emerald-300 hover:underline">support@mgzon.com</a></p>
209
- <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
210
- <p>Reach out to our support team for any inquiries or assistance.</p>
211
- <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
212
  </div>
213
  </div>
214
- <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
215
- <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
216
- <h4 class="font-semibold mb-1">Phone Support</h4>
217
- <p>+1-800-123-4567</p>
218
- <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
219
- <p>Contact our support team via phone for immediate assistance.</p>
220
- <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
221
  </div>
222
  </div>
223
- <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
224
- <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
225
- <h4 class="font-semibold mb-1">Community</h4>
226
- <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
227
- <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
228
- <p>Join our vibrant community to share ideas and collaborate.</p>
229
- <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
230
  </div>
231
  </div>
232
  </div>
233
- <div class="flex justify-center gap-6 mt-6">
234
- <a href="https://github.com/Mark-Lasfar/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-github"></i></a>
235
- <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
236
- <a href="https://www.facebook.com/people/Mark-Al-Asfar/pfbid02GMisUQ8AqWkNZjoKtWFHH1tbdHuVscN1cjcFnZWy9HkRaAsmanBfT6mhySAyqpg4l/" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-facebook"></i></a>
237
  </div>
238
- <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
239
  </div>
240
  </footer>
241
 
@@ -244,40 +251,12 @@
244
  <script src="/static/js/chat.js"></script>
245
  <script>
246
  AOS.init();
247
- document.addEventListener('DOMContentLoaded', () => {
248
- const prompts = document.querySelectorAll('.prompt-item');
249
- prompts.forEach(prompt => {
250
- prompt.addEventListener('click', () => {
251
- document.getElementById('userInput').value = prompt.getAttribute('data-prompt');
252
- document.getElementById('footerForm').dispatchEvent(new Event('submit'));
253
- });
254
- });
255
- document.getElementById('homeBtn').addEventListener('click', () => {
256
- window.location.href = '/';
257
- });
258
- document.getElementById('clearBtn').addEventListener('click', () => {
259
- document.getElementById('chatBox').innerHTML = '';
260
- document.getElementById('initialContent').classList.remove('hidden');
261
- document.getElementById('chatArea').classList.add('hidden');
262
- });
263
- document.getElementById('fileBtn').addEventListener('click', () => {
264
- document.getElementById('fileInput').click();
265
- });
266
- document.getElementById('audioBtn').addEventListener('click', () => {
267
- document.getElementById('audioInput').click();
268
- });
269
- {% if not user %}
270
- document.getElementById('loginBtn').addEventListener('click', () => {
271
- window.location.href = '/login';
272
- });
273
- {% endif %}
274
- function showCardDetails(cardId) {
275
- document.getElementById(`${cardId}-details`).classList.remove('hidden');
276
- }
277
- function closeCardDetails(cardId) {
278
- document.getElementById(`${cardId}-details`).classList.add('hidden');
279
- }
280
- });
281
  </script>
282
  </body>
283
  </html>
 
123
  </p>
124
  <!-- Prompts -->
125
  <div class="prompts w-full max-w-md mx-auto grid gap-2">
126
+ <div class="prompt-item" data-prompt="What's the capital of France?">
127
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
128
  <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M18.66 18.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M18.66 5.34l1.41-1.41" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
129
  <circle cx="12" cy="12" r="4" stroke-width="2" />
130
  </svg>
131
  <span>What's the capital of France?</span>
132
  </div>
133
+ <div class="prompt-item" data-prompt="Generate a Python script for a simple web server">
134
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
135
  <path d="M4 7h16M4 12h16M4 17h16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
136
  </svg>
137
  <span>Generate a Python script for a simple web server</span>
138
  </div>
139
+ <div class="prompt-item" data-prompt="Analyze this image for me">
140
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
141
  <path d="M3 12h18M12 3v18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
142
  </svg>
143
  <span>Analyze this image for me</span>
144
  </div>
145
+ <div class="prompt-item" data-prompt="Convert this text to audio">
146
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
147
  <path d="M12 3v18M7 12h10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
148
  </svg>
 
157
  <div id="chatBox" class="flex-col" aria-live="polite"></div>
158
  </div>
159
  <!-- Footer Form -->
160
+ <form id="footerForm" class="flex" autocomplete="off">
161
  <div id="inputContainer" class="w-full">
162
+ <textarea id="userInput" placeholder="Ask anything..." required></textarea>
163
  <div id="rightIconGroup" class="flex gap-2">
164
  <button type="button" id="fileBtn" class="icon-btn" aria-label="Upload File" title="Upload File">
165
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
 
168
  </svg>
169
  </button>
170
  <input type="file" id="fileInput" accept="image/*,.mp3,.wav" style="display: none;" />
171
+ <button type="button" id="audioBtn" class="icon-btn" aria-label="Upload Audio File" title="Upload Audio File">
172
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
173
  <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
174
  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
 
176
  </svg>
177
  </button>
178
  <input type="file" id="audioInput" accept="audio/*" style="display: none;" />
179
+ <button type="button" id="voiceBtn" class="icon-btn" aria-label="Record Voice" title="Hold to Record Voice">
180
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
181
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
182
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
183
+ <path d="M12 19v4" />
184
+ </svg>
185
+ </button>
186
  <button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send" title="Send">
187
  <svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
188
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
 
201
  </main>
202
 
203
  <!-- Footer -->
204
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 w-full">
205
  <div class="container max-w-6xl mx-auto text-center">
206
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-16 h-16 mx-auto mb-4 animate-pulse">
207
+ <p class="mb-2 text-sm">
208
  Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
209
  | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
210
  </p>
211
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
212
+ <div class="glass p-3 cursor-pointer" onclick="showCardDetails('email')">
213
+ <i class="bx bx-mail-send text-2xl text-emerald-300 mb-1"></i>
214
+ <h4 class="font-semibold mb-1 text-sm">Email Us</h4>
215
+ <p class="text-xs"><a href="mailto:support@mgzon.com" class="text-emerald-300 hover:underline">support@mgzon.com</a></p>
216
+ <div id="email-details" class="hidden mt-2 p-3 bg-gray-700/80 rounded-lg">
217
+ <p class="text-xs">Reach out to our support team for any inquiries or assistance.</p>
218
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
219
  </div>
220
  </div>
221
+ <div class="glass p-3 cursor-pointer" onclick="showCardDetails('phone')">
222
+ <i class="bx bx-phone text-2xl text-emerald-300 mb-1"></i>
223
+ <h4 class="font-semibold mb-1 text-sm">Phone Support</h4>
224
+ <p class="text-xs">+1-800-123-4567</p>
225
+ <div id="phone-details" class="hidden mt-2 p-3 bg-gray-700/80 rounded-lg">
226
+ <p class="text-xs">Contact our support team via phone for immediate assistance.</p>
227
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
228
  </div>
229
  </div>
230
+ <div class="glass p-3 cursor-pointer" onclick="showCardDetails('community')">
231
+ <i class="bx bx-group text-2xl text-emerald-300 mb-1"></i>
232
+ <h4 class="font-semibold mb-1 text-sm">Community</h4>
233
+ <p class="text-xs"><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
234
+ <div id="community-details" class="hidden mt-2 p-3 bg-gray-700/80 rounded-lg">
235
+ <p class="text-xs">Join our vibrant community to share ideas and collaborate.</p>
236
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
237
  </div>
238
  </div>
239
  </div>
240
+ <div class="flex justify-center gap-4 mt-4">
241
+ <a href="https://github.com/Mark-Lasfar/MGZon" class="text-xl text-white hover:text-emerald-300 transition"><i class="bx bxl-github"></i></a>
242
+ <a href="https://x.com/MGZon" class="text-xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
243
+ <a href="https://www.facebook.com/people/Mark-Al-Asfar/pfbid02GMisUQ8AqWkNZjoKtWFHH1tbdHuVscN1cjcFnZWy9HkRaAsmanBfT6mhySAyqpg4l/" class="text-xl text-white hover:text-emerald-300 transition"><i class="bx bxl-facebook"></i></a>
244
  </div>
245
+ <p class="mt-4 text-xs">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
246
  </div>
247
  </footer>
248
 
 
251
  <script src="/static/js/chat.js"></script>
252
  <script>
253
  AOS.init();
254
+ function showCardDetails(cardId) {
255
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
256
+ }
257
+ function closeCardDetails(cardId) {
258
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
259
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  </script>
261
  </body>
262
  </html>