-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstandalone.html
More file actions
484 lines (447 loc) · 26.3 KB
/
Copy pathstandalone.html
File metadata and controls
484 lines (447 loc) · 26.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Math Bro - Standalone</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<style>
body { font-family: 'Outfit', sans-serif; }
.glass-panel {
background: linear-gradient(145deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.9));
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, 0.3); border-radius: 10px; }
@keyframes pulse-ring {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
}
.recording-pulse { animation: pulse-ring 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
</style>
<!-- Polyfill for process.env -->
<script>
window.process = {
env: {
API_KEY: "" // TODO: INSERT YOUR GEMINI API KEY HERE
}
};
</script>
<!-- Import Map for Modules with ?bundle to ensure they work in browser -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.2.0?bundle",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?bundle",
"@google/genai": "https://esm.sh/@google/genai?bundle",
"react-markdown": "https://esm.sh/react-markdown@9?bundle"
}
}
</script>
<!-- Babel for in-browser compilation -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body class="bg-[#0B0E14] text-slate-200">
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="typescript,react">
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';
import { GoogleGenAI } from '@google/genai';
import ReactMarkdown from 'react-markdown';
// --- TYPES ---
const Role = {
USER: 'user',
MODEL: 'model'
};
const MessageType = {
TEXT: 'text',
IMAGE: 'image',
AUDIO: 'audio'
};
// --- CONSTANTS ---
const GEMINI_MODEL = 'gemini-3-flash-preview';
const THEMES = [
{
id: 'cyber-dashboard',
name: 'Cyber Dark',
primary: 'bg-blue-600',
gradient: 'from-blue-500 to-indigo-600',
background: 'bg-[#0B0E14]',
panel: 'bg-[#151A25]',
text: 'text-slate-200',
bubbleUser: 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white',
bubbleModel: 'bg-[#1E293B] text-slate-200 border border-slate-700/50',
},
{
id: 'neon-purple',
name: 'Neon Flux',
primary: 'bg-fuchsia-600',
gradient: 'from-fuchsia-600 to-purple-600',
background: 'bg-[#1a0b2e]',
panel: 'bg-[#2d1b4e]',
text: 'text-fuchsia-50',
bubbleUser: 'bg-gradient-to-r from-fuchsia-600 to-purple-600 text-white',
bubbleModel: 'bg-[#3b2a5f] text-fuchsia-100 border border-fuchsia-800/50',
},
{
id: 'emerald-matrix',
name: 'Matrix',
primary: 'bg-emerald-600',
gradient: 'from-emerald-500 to-teal-600',
background: 'bg-[#022c22]',
panel: 'bg-[#064e3b]',
text: 'text-emerald-50',
bubbleUser: 'bg-gradient-to-r from-emerald-600 to-teal-600 text-white',
bubbleModel: 'bg-[#065f46] text-emerald-100 border border-emerald-800/50',
}
];
const TRANSLATIONS = {
en: {
welcome: "Hello! I'm Math Bro. I can solve algebra, geometry, and calculus problems. Upload an image or use the mic to speak.",
inputPlaceholder: "Type a math problem...",
recording: "Listening...",
releaseToSend: "Tap to Stop",
thinking: "Calculating...",
settings: "Settings",
language: "Language",
theme: "Theme",
clearChat: "Clear Chat",
uploadImage: "Upload Image",
send: "Send",
appName: "Math Bro",
tagline: "AI Math Assistant",
error429: "Too many requests. Retrying automatically or please wait a moment.",
madeBy: "Made by Rayane Maguenaoui"
},
fr: {
welcome: "Salut! Je suis Math Bro. Je peux résoudre des problèmes d'algèbre, de géométrie et de calcul. Téléchargez une image ou utilisez le micro.",
inputPlaceholder: "Écrivez un problème de maths...",
recording: "Écoute en cours...",
releaseToSend: "Appuyez pour arrêter",
thinking: "Calcul en cours...",
settings: "Paramètres",
language: "Langue",
theme: "Thème",
clearChat: "Effacer",
uploadImage: "Télécharger une image",
send: "Envoyer",
appName: "Math Bro",
tagline: "Assistant Math IA",
error429: "Trop de requêtes. Nouvelle tentative automatique ou veuillez patienter.",
madeBy: "Créé par Rayane Maguenaoui"
},
ar: {
welcome: "مرحباً! أنا ماث برو. يمكنني حل مسائل الجبر والهندسة وحساب التفاضل والتكامل. قم بتحميل صورة أو استخدم الميكروفون للتحدث.",
inputPlaceholder: "اكتب مسألة رياضية...",
recording: "جاري الاستماع...",
releaseToSend: "اضغط للإيقاف",
thinking: "جاري الحساب...",
settings: "الإعدادات",
language: "اللغة",
theme: "المظهر",
clearChat: "مسح المحادثة",
uploadImage: "رفع صورة",
send: "إرسال",
appName: "ماث برو",
tagline: "مساعد الرياضيات الذكي",
error429: "طلبات كثيرة جداً. جاري إعادة المحاولة تلقائياً أو يرجى الانتظار قليلاً.",
madeBy: "صنع بواسطة Rayane Maguenaoui"
}
};
// --- SERVICES ---
const solveMathProblem = async (currentMessage, attachments, history, language) => {
const t = TRANSLATIONS[language];
const apiKey = window.process?.env?.API_KEY;
if (!apiKey) {
alert("Please edit the HTML file and insert your Google Gemini API Key in the script tag at the top!");
return "Error: API_KEY is missing. Check the HTML file source code.";
}
const ai = new GoogleGenAI({ apiKey });
try {
const relevantHistory = history.slice(-10).map(msg => ({
role: msg.role === Role.USER ? 'user' : 'model',
parts: [{ text: msg.content }]
}));
const parts = [];
if (currentMessage) parts.push({ text: currentMessage });
attachments.forEach(att => {
parts.push({
inlineData: {
mimeType: att.mimeType,
data: att.data
}
});
});
let langInstruction = language === 'ar' ? "Arabic" : (language === 'fr' ? "French" : "English");
const systemInstruction = `You are "Math Bro", an expert Math and Geometry tutor.
1. Respond strictly in ${langInstruction}.
2. Solve problems step-by-step.
3. If an image is provided, analyze the geometry, graphs, or handwritten equations carefully.
4. Use LaTeX formatting for math equations (wrap in single $ for inline, double $$ for block).
5. Be encouraging, clear, and concise.`;
const response = await ai.models.generateContent({
model: GEMINI_MODEL,
contents: [
...relevantHistory,
{ role: 'user', parts: parts }
],
config: {
systemInstruction: systemInstruction,
thinkingConfig: { thinkingBudget: 1024 },
temperature: 0.1,
}
});
return response.text || "Error: No response generated.";
} catch (error) {
console.error(`Gemini API Error:`, error);
return `Error: ${error.message || "Something went wrong."}`;
}
};
// --- COMPONENTS ---
const MathRenderer = ({ content, isDark }) => {
// Using ReactMarkdown without plugins for stability in standalone mode.
// Complex plugins like rehype-katex fail often in browser-only imports.
return (
<div className={`markdown-body ${isDark ? 'text-slate-100' : 'text-slate-800'} text-sm md:text-base leading-relaxed overflow-x-auto`}>
<ReactMarkdown
components={{
p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
code: ({ children }) => <code className="bg-slate-700 px-1 rounded">{children}</code>
}}
>
{content}
</ReactMarkdown>
</div>
);
};
const InputArea = ({ onSend, isLoading, themePrimary, themeGradient, language, t }) => {
const [inputText, setInputText] = useState('');
const [isRecording, setIsRecording] = useState(false);
const [attachment, setAttachment] = useState(null);
const recognitionRef = useRef(null);
const fileInputRef = useRef(null);
const textareaRef = useRef(null);
useEffect(() => {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
recognitionRef.current = new SpeechRecognition();
recognitionRef.current.continuous = true;
recognitionRef.current.interimResults = true;
const langMap = {
'en': 'en-US',
'fr': 'fr-FR',
'ar': 'ar-SA'
};
recognitionRef.current.lang = langMap[language];
recognitionRef.current.onresult = (event) => {
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
finalTranscript += event.results[i][0].transcript;
}
}
if (finalTranscript) {
setInputText(prev => prev + (prev ? ' ' : '') + finalTranscript);
}
};
recognitionRef.current.onend = () => setIsRecording(false);
}
}, [language]);
const toggleRecording = () => {
if (isRecording) {
recognitionRef.current?.stop();
setIsRecording(false);
} else {
if (!recognitionRef.current) {
alert("Speech recognition not supported in this browser.");
return;
}
recognitionRef.current.start();
setIsRecording(true);
}
};
const handleFileSelect = (e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result.split(',')[1];
setAttachment({
mimeType: file.type,
data: base64String,
type: MessageType.IMAGE
});
};
reader.readAsDataURL(file);
}
};
const handleSendText = () => {
if (!inputText.trim() && !attachment) return;
const attachments = attachment ? [attachment] : [];
onSend(inputText, attachments);
setInputText('');
setAttachment(null);
};
return (
<div className={`p-4 md:p-6 glass-panel border-t-0 rounded-t-3xl md:rounded-3xl md:mb-6 md:mx-6`}>
{attachment && (
<div className="relative inline-block mb-3 animate-fade-in">
<img src={`data:${attachment.mimeType};base64,${attachment.data}`} className="h-24 w-24 object-cover rounded-xl border-2 border-slate-600/50 shadow-lg" />
<button onClick={() => setAttachment(null)} className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1.5 w-7 h-7 flex items-center justify-center shadow-lg"><i className="fas fa-times text-xs"></i></button>
</div>
)}
<div className="flex items-end gap-3">
<button onClick={() => fileInputRef.current?.click()} className="p-3.5 rounded-2xl bg-slate-700/50 text-slate-300 hover:text-white transition-all"><i className="fas fa-image text-lg"></i></button>
<input type="file" ref={fileInputRef} className="hidden" accept="image/*" onChange={handleFileSelect} />
<div className="flex-1 relative">
<textarea ref={textareaRef} value={inputText} onChange={(e) => setInputText(e.target.value)} placeholder={isRecording ? t.recording : t.inputPlaceholder} dir={language === 'ar' ? 'rtl' : 'ltr'} className="w-full bg-slate-800/50 border border-slate-700/50 text-slate-100 rounded-2xl px-4 py-3.5 pr-12 focus:outline-none resize-none" rows={1} />
<button onClick={toggleRecording} className={`absolute ${language === 'ar' ? 'left-3' : 'right-3'} bottom-2.5 p-1.5 transition-all ${isRecording ? 'text-red-400 recording-pulse' : 'text-slate-400'}`}><i className={`fas ${isRecording ? 'fa-stop-circle' : 'fa-microphone'} text-lg`}></i></button>
</div>
<button onClick={handleSendText} disabled={isLoading || (!inputText.trim() && !attachment)} className={`p-3.5 rounded-2xl w-14 h-14 flex items-center justify-center transition-all shadow-lg bg-gradient-to-r ${themeGradient} text-white`}><i className={`fas ${language === 'ar' ? 'fa-paper-plane fa-flip-horizontal' : 'fa-paper-plane'}`}></i></button>
</div>
</div>
);
};
const App = () => {
const [currentTheme, setCurrentTheme] = useState(THEMES[0]);
const [language, setLanguage] = useState('en');
const [showSettings, setShowSettings] = useState(false);
const t = TRANSLATIONS[language];
const [messages, setMessages] = useState([{ id: 'welcome', role: Role.MODEL, content: t.welcome, timestamp: Date.now() }]);
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef(null);
const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
useEffect(() => { scrollToBottom(); }, [messages, isLoading]);
useEffect(() => {
// Reset welcome msg on lang change
if(messages.length === 1 && messages[0].id === 'welcome') {
setMessages([{ id: 'welcome', role: Role.MODEL, content: t.welcome, timestamp: Date.now() }]);
}
}, [language]);
const handleSendMessage = async (text, attachments) => {
const newUserMessage = { id: Math.random().toString(), role: Role.USER, content: text, attachments: attachments, timestamp: Date.now() };
setMessages(prev => [...prev, newUserMessage]);
setIsLoading(true);
const solutionText = await solveMathProblem(text, attachments, messages, language);
const newModelMessage = { id: Math.random().toString(), role: Role.MODEL, content: solutionText, timestamp: Date.now() };
setMessages(prev => [...prev, newModelMessage]);
setIsLoading(false);
};
const handleClearChat = () => {
setMessages([{ id: Math.random().toString(), role: Role.MODEL, content: t.welcome, timestamp: Date.now() }]);
setShowSettings(false);
};
return (
<div className={`h-screen w-full flex overflow-hidden ${currentTheme.background} text-slate-200 font-sans`} dir={language === 'ar' ? 'rtl' : 'ltr'}>
<aside className={`fixed inset-y-0 ${language === 'ar' ? 'right-0 border-l' : 'left-0 border-r'} border-white/5 w-20 md:w-64 glass-panel z-50 flex flex-col items-center md:items-stretch transition-all duration-300 hidden md:flex`}>
<div className="p-6 flex items-center gap-3 justify-center md:justify-start">
<div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${currentTheme.gradient} flex items-center justify-center text-white shadow-lg`}><i className="fas fa-cube text-xl"></i></div>
<div className="hidden md:block">
<h1 className="font-bold text-xl tracking-tight text-white">{t.appName}</h1>
<p className="text-xs text-slate-400">{t.tagline}</p>
</div>
</div>
<nav className="flex-1 px-4 py-6 space-y-2">
<button className={`w-full flex items-center gap-4 px-4 py-3 rounded-xl bg-gradient-to-r ${currentTheme.gradient} text-white shadow-lg`}><i className="fas fa-message w-6 text-center"></i><span className="hidden md:block font-medium">Chat</span></button>
<button onClick={() => setShowSettings(!showSettings)} className="w-full flex items-center gap-4 px-4 py-3 rounded-xl hover:bg-white/5 text-slate-400 hover:text-white transition-all"><i className="fas fa-cog w-6 text-center"></i><span className="hidden md:block font-medium">{t.settings}</span></button>
</nav>
<div className="p-6 border-t border-white/5 bg-black/20">
<div className="flex items-center justify-center">
<p className="text-[10px] font-bold tracking-[0.2em] text-slate-500 uppercase text-center hover:text-slate-400 transition-colors">{t.madeBy}</p>
</div>
</div>
</aside>
<div className={`flex-1 flex flex-col h-full relative ${language === 'ar' ? 'md:mr-64' : 'md:ml-64'}`}>
<header className="md:hidden h-16 glass-panel flex items-center justify-between px-4 z-40">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-lg bg-gradient-to-br ${currentTheme.gradient} flex items-center justify-center text-white shadow-md`}><i className="fas fa-cube text-sm"></i></div>
<div className="flex flex-col">
<h1 className="font-bold text-lg text-white leading-none">{t.appName}</h1>
<p className="text-[9px] text-slate-400 font-bold uppercase tracking-widest mt-0.5">{t.madeBy}</p>
</div>
</div>
<button onClick={() => setShowSettings(!showSettings)} className="text-slate-300 p-2 hover:bg-white/5 rounded-full"><i className="fas fa-bars text-xl"></i></button>
</header>
<main className="flex-1 overflow-y-auto p-4 md:p-8 scroll-smooth relative">
<div className="max-w-4xl mx-auto space-y-6 pb-4">
{messages.map((msg) => {
const isUser = msg.role === Role.USER;
return (
<div key={msg.id} className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-fade-in`}>
{!isUser && (
<div className={`w-9 h-9 rounded-full bg-gradient-to-br ${currentTheme.gradient} flex-shrink-0 flex items-center justify-center text-white text-xs shadow-lg ${language === 'ar' ? 'ml-3' : 'mr-3'} mt-1`}><i className="fas fa-robot text-sm"></i></div>
)}
<div className={`max-w-[85%] md:max-w-[75%] rounded-2xl px-6 py-4 shadow-xl backdrop-blur-md ${isUser ? currentTheme.bubbleUser : currentTheme.bubbleModel}`}>
{msg.attachments && msg.attachments.length > 0 && (
<div className="mb-4">{msg.attachments.map((att, idx) => att.type === MessageType.IMAGE && (<img key={idx} src={`data:${att.mimeType};base64,${att.data}`} className="rounded-xl max-h-72 border border-white/20 shadow-2xl" />))}</div>
)}
<div className={`${isUser ? 'text-white' : 'text-slate-200'}`}>
{isUser ? <p className="whitespace-pre-wrap leading-relaxed font-medium">{msg.content}</p> : <MathRenderer content={msg.content} isDark={true} />}
</div>
<div className={`text-[10px] mt-2 opacity-50 flex items-center gap-1 ${isUser ? 'justify-end text-white' : 'justify-start text-slate-400'}`}>{new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
</div>
</div>
);
})}
{isLoading && (
<div className="flex justify-start items-center gap-3">
<div className={`w-9 h-9 rounded-full bg-gradient-to-br ${currentTheme.gradient} flex items-center justify-center text-white text-xs shadow-lg`}><i className="fas fa-robot text-sm"></i></div>
<div className={`${currentTheme.bubbleModel} px-6 py-3.5 rounded-2xl flex items-center gap-4 shadow-2xl border border-white/5`}>
<div className="flex space-x-1.5"><div className="w-2.5 h-2.5 rounded-full bg-blue-500 animate-bounce"></div><div className="w-2.5 h-2.5 rounded-full bg-purple-500 animate-bounce delay-100"></div><div className="w-2.5 h-2.5 rounded-full bg-indigo-500 animate-bounce delay-200"></div></div>
<span className="text-sm text-slate-300 font-semibold tracking-wide">{t.thinking}</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</main>
<InputArea onSend={handleSendMessage} isLoading={isLoading} themePrimary={currentTheme.primary} themeGradient={currentTheme.gradient} language={language} t={t} />
{showSettings && (
<div className="fixed inset-0 bg-black/80 backdrop-blur-md z-[100] flex items-center justify-center p-4">
<div className="bg-[#1E293B] border border-white/10 rounded-[2.5rem] w-full max-w-md p-8 shadow-[0_0_50px_rgba(0,0,0,0.5)] transform transition-all scale-100">
<div className="flex justify-between items-center mb-8">
<h2 className="text-2xl font-bold text-white flex items-center gap-3"><i className="fas fa-sliders text-blue-500"></i> {t.settings}</h2>
<button onClick={() => setShowSettings(false)} className="w-10 h-10 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center transition-all"><i className="fas fa-times text-lg"></i></button>
</div>
<div className="mb-8">
<label className="block text-sm font-bold text-slate-500 uppercase tracking-widest mb-4 ml-1">{t.language}</label>
<div className="grid grid-cols-3 gap-3">
{['en', 'fr', 'ar'].map((lang) => (
<button key={lang} onClick={() => setLanguage(lang)} className={`py-3 rounded-2xl text-sm font-bold transition-all border-2 ${language === lang ? `bg-gradient-to-r ${currentTheme.gradient} text-white border-transparent` : 'bg-slate-800/50 text-slate-400 border-slate-700/50'}`}>{lang === 'en' ? 'EN' : lang === 'fr' ? 'FR' : 'AR'}</button>
))}
</div>
</div>
<div className="mb-10">
<label className="block text-sm font-bold text-slate-500 uppercase tracking-widest mb-4 ml-1">{t.theme}</label>
<div className="grid grid-cols-1 gap-3">
{THEMES.map(theme => (
<button key={theme.id} onClick={() => setCurrentTheme(theme)} className={`flex items-center p-4 rounded-2xl border-2 transition-all ${currentTheme.id === theme.id ? `bg-slate-800/80 border-blue-500 shadow-inner` : 'bg-transparent border-slate-700/30 hover:bg-slate-800/40'}`}>
<div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${theme.gradient} mr-4 shadow-lg`}></div>
<span className={`flex-1 text-left font-bold ${currentTheme.id === theme.id ? 'text-white' : 'text-slate-400'}`}>{theme.name}</span>
</button>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<button onClick={handleClearChat} className="w-full py-4 rounded-2xl bg-red-500/10 text-red-400 border border-red-500/20 hover:bg-red-500/20 transition-all flex items-center justify-center gap-3 font-bold"><i className="fas fa-trash-alt"></i> {t.clearChat}</button>
</div>
</div>
</div>
)}
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>