Mark-Lasfar commited on
Commit
1b97733
·
1 Parent(s): d28afad

update main.py

Browse files
Files changed (3) hide show
  1. main.py +14 -6
  2. static/js/chat.js +121 -55
  3. templates/index.html +140 -84
main.py CHANGED
@@ -31,7 +31,7 @@ import re
31
  import anyio
32
 
33
  # Setup logging
34
- logging.basicConfig(level=logging.DEBUG) # غيّرنا لـ DEBUG عشان نعرف نتبع كل حاجة
35
  logger = logging.getLogger(__name__)
36
  logger.info("Starting application...")
37
  logger.debug("Files in current directory: %s", os.listdir(os.getcwd()))
@@ -73,7 +73,7 @@ async def setup_mongo_index():
73
  logger.error(f"Failed to create MongoDB index: {e}")
74
 
75
  # Jinja2 setup
76
- os.makedirs("templates", exist_ok=True) # تأكد إن مجلد templates موجود
77
  templates = Jinja2Templates(directory="templates")
78
  templates.env.filters['markdown'] = lambda text: markdown2.markdown(text)
79
 
@@ -117,28 +117,32 @@ app.add_middleware(
117
  allow_origins=[
118
  "https://mgzon-mgzon-app.hf.space",
119
  "http://localhost:7860",
120
- "http://localhost:8000", # أضفنا ده للتستيج المحلي
121
  "https://mgzon-mgzon-app.hf.space/auth/google/callback",
122
  "https://mgzon-mgzon-app.hf.space/auth/github/callback",
123
  ],
124
  allow_credentials=True,
125
  allow_methods=["GET", "POST", "OPTIONS", "PUT", "DELETE"],
126
- allow_headers=["Accept", "Content-Type", "Authorization", "X-Requested-With"],
127
  )
128
  logger.debug("CORS middleware configured with allowed origins")
129
 
130
  # Include routers
131
  app.include_router(api_router)
132
- get_auth_router(app) # Add OAuth and auth routers
133
  logger.debug("API and auth routers included")
134
 
135
  # Add logout endpoint
136
  @app.get("/logout")
137
  async def logout(request: Request):
138
  logger.info("User logout requested")
 
139
  request.session.clear()
 
140
  response = RedirectResponse("/login")
141
  response.delete_cookie("access_token")
 
 
142
  return response
143
 
144
  # Debug routes endpoint
@@ -200,7 +204,11 @@ async def handle_oauth_error(request: Request, exc: GetIdEmailError):
200
  @app.get("/", response_class=HTMLResponse)
201
  async def root(request: Request, user: User = Depends(current_active_user)):
202
  logger.debug(f"Root endpoint accessed by user: {user.email if user else 'Anonymous'}")
203
- return templates.TemplateResponse("index.html", {"request": request, "user": user})
 
 
 
 
204
 
205
  # Google verification
206
  @app.get("/google97468ef1f6b6e804.html", response_class=PlainTextResponse)
 
31
  import anyio
32
 
33
  # Setup logging
34
+ logging.basicConfig(level=logging.DEBUG)
35
  logger = logging.getLogger(__name__)
36
  logger.info("Starting application...")
37
  logger.debug("Files in current directory: %s", os.listdir(os.getcwd()))
 
73
  logger.error(f"Failed to create MongoDB index: {e}")
74
 
75
  # Jinja2 setup
76
+ os.makedirs("templates", exist_ok=True)
77
  templates = Jinja2Templates(directory="templates")
78
  templates.env.filters['markdown'] = lambda text: markdown2.markdown(text)
79
 
 
117
  allow_origins=[
118
  "https://mgzon-mgzon-app.hf.space",
119
  "http://localhost:7860",
120
+ "http://localhost:8000",
121
  "https://mgzon-mgzon-app.hf.space/auth/google/callback",
122
  "https://mgzon-mgzon-app.hf.space/auth/github/callback",
123
  ],
124
  allow_credentials=True,
125
  allow_methods=["GET", "POST", "OPTIONS", "PUT", "DELETE"],
126
+ allow_headers=["Accept", "Content-Type", "Authorization", "X-Requested-With", "X-Session-ID"],
127
  )
128
  logger.debug("CORS middleware configured with allowed origins")
129
 
130
  # Include routers
131
  app.include_router(api_router)
132
+ get_auth_router(app)
133
  logger.debug("API and auth routers included")
134
 
135
  # Add logout endpoint
136
  @app.get("/logout")
137
  async def logout(request: Request):
138
  logger.info("User logout requested")
139
+ session_data = request.session.copy()
140
  request.session.clear()
141
+ logger.debug(f"Cleared session data: {session_data}")
142
  response = RedirectResponse("/login")
143
  response.delete_cookie("access_token")
144
+ response.delete_cookie("session")
145
+ logger.debug("Session and access_token cookies deleted")
146
  return response
147
 
148
  # Debug routes endpoint
 
204
  @app.get("/", response_class=HTMLResponse)
205
  async def root(request: Request, user: User = Depends(current_active_user)):
206
  logger.debug(f"Root endpoint accessed by user: {user.email if user else 'Anonymous'}")
207
+ return templates.TemplateResponse("index.html", {
208
+ "request": request,
209
+ "user": user,
210
+ "is_authenticated": user is not None
211
+ })
212
 
213
  # Google verification
214
  @app.get("/google97468ef1f6b6e804.html", response_class=PlainTextResponse)
static/js/chat.js CHANGED
@@ -33,51 +33,24 @@ const uiElements = {
33
  settingsModal: document.getElementById('settingsModal'),
34
  closeSettingsBtn: document.getElementById('closeSettingsBtn'),
35
  settingsForm: document.getElementById('settingsForm'),
36
- historyToggle: document.getElementById('historyToggle')
37
- };
38
-
39
- // Track state
40
- let streamMsg = null;
41
- let conversationHistory = JSON.parse(sessionStorage.getItem('conversationHistory') || '[]');
42
- let currentAssistantText = '';
43
- let isRequestActive = false;
44
- let abortController = null;
45
- let mediaRecorder = null;
46
- let audioChunks = [];
47
- let isRecording = false;
48
- let currentConversationId = window.conversationId || null;
49
- let currentConversationTitle = window.conversationTitle || null;
50
- let isSidebarOpen = false;
51
-
52
- // Auto-resize textarea
53
- function autoResizeTextarea() {
54
- if (uiElements.input) {
55
- uiElements.input.style.height = 'auto';
56
- uiElements.input.style.height = `${Math.min(uiElements.input.scrollHeight, 200)}px`;
57
- }
58
- }
59
-
60
- // Detect Arabic text
61
- function isArabicText(text) {
62
- return /[\u0600-\u06FF]/.test(text);
63
- }
64
-
65
- // Initialize page
66
- document.addEventListener('DOMContentLoaded', async () => {
67
- AOS.init({
68
  duration: 800,
69
  easing: 'ease-out-cubic',
70
  once: true,
71
  offset: 50,
72
  });
73
  if (currentConversationId && checkAuth()) {
 
74
  await loadConversation(currentConversationId);
75
  } else if (conversationHistory.length > 0) {
 
76
  enterChatView();
77
- conversationHistory.forEach(msg => addMsg(msg.role, msg.content));
78
- }
79
- if (checkAuth()) {
80
- await loadConversations();
 
 
81
  }
82
  autoResizeTextarea();
83
  updateSendButtonState();
@@ -91,7 +64,22 @@ document.addEventListener('DOMContentLoaded', async () => {
91
 
92
  // Check authentication token
93
  function checkAuth() {
94
- return localStorage.getItem('token');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
 
97
  // Update send button state
@@ -156,11 +144,15 @@ function addMsg(who, text) {
156
  const div = document.createElement('div');
157
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
158
  div.dataset.text = text;
 
159
  renderMarkdown(div);
160
  if (uiElements.chatBox) {
161
  uiElements.chatBox.appendChild(div);
162
  uiElements.chatBox.classList.remove('hidden');
163
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
 
 
 
164
  }
165
  return div;
166
  }
@@ -226,14 +218,22 @@ function previewFile() {
226
 
227
  // Voice recording
228
  function startVoiceRecording() {
229
- if (isRequestActive || isRecording) return;
 
 
 
 
230
  isRecording = true;
231
  if (uiElements.sendBtn) uiElements.sendBtn.classList.add('recording');
232
  navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
233
  mediaRecorder = new MediaRecorder(stream);
234
  audioChunks = [];
235
  mediaRecorder.start();
236
- mediaRecorder.addEventListener('dataavailable', event => audioChunks.push(event.data));
 
 
 
 
237
  }).catch(err => {
238
  console.error('Error accessing microphone:', err);
239
  alert('Failed to access microphone. Please check permissions.');
@@ -248,6 +248,7 @@ function stopVoiceRecording() {
248
  if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
249
  isRecording = false;
250
  mediaRecorder.addEventListener('stop', async () => {
 
251
  const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
252
  const formData = new FormData();
253
  formData.append('file', audioBlob, 'voice-message.webm');
@@ -306,6 +307,8 @@ async function submitAudioMessage(formData) {
306
  async function sendRequest(endpoint, body, headers = {}) {
307
  const token = checkAuth();
308
  if (token) headers['Authorization'] = `Bearer ${token}`;
 
 
309
  try {
310
  const response = await fetch(endpoint, {
311
  method: 'POST',
@@ -330,9 +333,7 @@ async function sendRequest(endpoint, body, headers = {}) {
330
  }
331
  return response;
332
  } catch (error) {
333
- if (error.name === 'AbortError') {
334
- throw new Error('Request was aborted');
335
- }
336
  throw error;
337
  }
338
  }
@@ -362,6 +363,11 @@ function handleRequestError(error) {
362
  if (streamMsg) {
363
  streamMsg.querySelector('.loading')?.remove();
364
  streamMsg.dataset.text = `Error: ${error.message || 'An error occurred during the request.'}`;
 
 
 
 
 
365
  renderMarkdown(streamMsg);
366
  streamMsg.dataset.done = '1';
367
  streamMsg = null;
@@ -372,6 +378,7 @@ function handleRequestError(error) {
372
  abortController = null;
373
  if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
374
  if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
 
375
  }
376
 
377
  // Load conversations for sidebar
@@ -627,6 +634,11 @@ async function submitMessage() {
627
  let outputFormat = 'text';
628
  let title = null;
629
 
 
 
 
 
 
630
  if (uiElements.fileInput?.files.length > 0) {
631
  const file = uiElements.fileInput.files[0];
632
  if (file.type.startsWith('image/')) {
@@ -660,8 +672,6 @@ async function submitMessage() {
660
  title: title
661
  };
662
  headers['Content-Type'] = 'application/json';
663
- } else {
664
- return;
665
  }
666
 
667
  enterChatView();
@@ -796,6 +806,31 @@ function stopStream(forceCancel = false) {
796
  if (uiElements.stopBtn) uiElements.stopBtn.style.pointerEvents = 'auto';
797
  }
798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
  // Settings Modal
800
  if (uiElements.settingsBtn) {
801
  uiElements.settingsBtn.addEventListener('click', () => {
@@ -933,36 +968,60 @@ if (uiElements.fileInput) uiElements.fileInput.addEventListener('change', previe
933
  if (uiElements.audioInput) uiElements.audioInput.addEventListener('change', previewFile);
934
 
935
  if (uiElements.sendBtn) {
936
- uiElements.sendBtn.addEventListener('mousedown', e => {
 
937
  if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
938
- startVoiceRecording();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  });
940
- uiElements.sendBtn.addEventListener('mouseup', () => isRecording && stopVoiceRecording());
941
- uiElements.sendBtn.addEventListener('mouseleave', () => isRecording && stopVoiceRecording());
942
- uiElements.sendBtn.addEventListener('touchstart', e => {
 
 
 
943
  e.preventDefault();
944
  if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
945
- startVoiceRecording();
 
 
946
  });
947
- uiElements.sendBtn.addEventListener('touchend', e => {
948
  e.preventDefault();
 
949
  if (isRecording) stopVoiceRecording();
950
  });
951
- uiElements.sendBtn.addEventListener('touchcancel', e => {
952
  e.preventDefault();
 
953
  if (isRecording) stopVoiceRecording();
954
  });
955
  }
956
 
957
  if (uiElements.form) {
958
- uiElements.form.addEventListener('submit', e => {
959
  e.preventDefault();
960
- if (!isRecording) submitMessage();
 
 
961
  });
962
  }
963
 
964
  if (uiElements.input) {
965
- uiElements.input.addEventListener('keydown', e => {
966
  if (e.key === 'Enter' && !e.shiftKey) {
967
  e.preventDefault();
968
  if (!isRecording && !uiElements.sendBtn.disabled) submitMessage();
@@ -1000,3 +1059,10 @@ if (uiElements.sidebarToggle) {
1000
  if (uiElements.newConversationBtn) {
1001
  uiElements.newConversationBtn.addEventListener('click', createNewConversation);
1002
  }
 
 
 
 
 
 
 
 
33
  settingsModal: document.getElementById('settingsModal'),
34
  closeSettingsBtn: document.getElementById('closeSettingsBtn'),
35
  settingsForm: document.getElementById('settingsForm'),
36
+ historyToggle: document.getElementBy AOS.init({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  duration: 800,
38
  easing: 'ease-out-cubic',
39
  once: true,
40
  offset: 50,
41
  });
42
  if (currentConversationId && checkAuth()) {
43
+ console.log('Loading conversation with ID:', currentConversationId);
44
  await loadConversation(currentConversationId);
45
  } else if (conversationHistory.length > 0) {
46
+ console.log('Restoring conversation history from sessionStorage:', conversationHistory);
47
  enterChatView();
48
+ conversationHistory.forEach(msg => {
49
+ console.log('Adding message from history:', msg);
50
+ addMsg(msg.role, msg.content);
51
+ });
52
+ } else {
53
+ console.log('No conversation history or ID, starting fresh');
54
  }
55
  autoResizeTextarea();
56
  updateSendButtonState();
 
64
 
65
  // Check authentication token
66
  function checkAuth() {
67
+ const token = localStorage.getItem('token');
68
+ console.log('Auth token:', token ? 'Found' : 'Not found');
69
+ return token;
70
+ }
71
+
72
+ // Handle session for non-logged-in users
73
+ async function handleSession() {
74
+ const sessionId = sessionStorage.getItem('session_id');
75
+ if (!sessionId) {
76
+ const newSessionId = crypto.randomUUID();
77
+ sessionStorage.setItem('session_id', newSessionId);
78
+ console.log('New session_id created:', newSessionId);
79
+ return newSessionId;
80
+ }
81
+ console.log('Existing session_id:', sessionId);
82
+ return sessionId;
83
  }
84
 
85
  // Update send button state
 
144
  const div = document.createElement('div');
145
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
146
  div.dataset.text = text;
147
+ console.log('Adding message:', { who, text });
148
  renderMarkdown(div);
149
  if (uiElements.chatBox) {
150
  uiElements.chatBox.appendChild(div);
151
  uiElements.chatBox.classList.remove('hidden');
152
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
153
+ console.log('Message added to chatBox:', div);
154
+ } else {
155
+ console.error('chatBox is null');
156
  }
157
  return div;
158
  }
 
218
 
219
  // Voice recording
220
  function startVoiceRecording() {
221
+ if (isRequestActive || isRecording) {
222
+ console.log('Voice recording blocked: Request active or already recording');
223
+ return;
224
+ }
225
+ console.log('Starting voice recording...');
226
  isRecording = true;
227
  if (uiElements.sendBtn) uiElements.sendBtn.classList.add('recording');
228
  navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
229
  mediaRecorder = new MediaRecorder(stream);
230
  audioChunks = [];
231
  mediaRecorder.start();
232
+ console.log('MediaRecorder started');
233
+ mediaRecorder.addEventListener('dataavailable', event => {
234
+ audioChunks.push(event.data);
235
+ console.log('Audio chunk received:', event.data);
236
+ });
237
  }).catch(err => {
238
  console.error('Error accessing microphone:', err);
239
  alert('Failed to access microphone. Please check permissions.');
 
248
  if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
249
  isRecording = false;
250
  mediaRecorder.addEventListener('stop', async () => {
251
+ console.log('Stopping voice recording, sending audio...');
252
  const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
253
  const formData = new FormData();
254
  formData.append('file', audioBlob, 'voice-message.webm');
 
307
  async function sendRequest(endpoint, body, headers = {}) {
308
  const token = checkAuth();
309
  if (token) headers['Authorization'] = `Bearer ${token}`;
310
+ headers['X-Session-ID'] = await handleSession();
311
+ console.log('Sending request to:', endpoint, 'with headers:', headers);
312
  try {
313
  const response = await fetch(endpoint, {
314
  method: 'POST',
 
333
  }
334
  return response;
335
  } catch (error) {
336
+ console.error('Send request error:', error);
 
 
337
  throw error;
338
  }
339
  }
 
363
  if (streamMsg) {
364
  streamMsg.querySelector('.loading')?.remove();
365
  streamMsg.dataset.text = `Error: ${error.message || 'An error occurred during the request.'}`;
366
+ const retryBtn = document.createElement('button');
367
+ retryBtn.innerText = 'Retry';
368
+ retryBtn.className = 'retry-btn text-sm text-blue-400 hover:text-blue-600';
369
+ retryBtn.onclick = () => submitMessage();
370
+ streamMsg.appendChild(retryBtn);
371
  renderMarkdown(streamMsg);
372
  streamMsg.dataset.done = '1';
373
  streamMsg = null;
 
378
  abortController = null;
379
  if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
380
  if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
381
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
382
  }
383
 
384
  // Load conversations for sidebar
 
634
  let outputFormat = 'text';
635
  let title = null;
636
 
637
+ if (!message && !uiElements.fileInput?.files.length && !uiElements.audioInput?.files.length) {
638
+ console.log('No message, file, or audio to send');
639
+ return;
640
+ }
641
+
642
  if (uiElements.fileInput?.files.length > 0) {
643
  const file = uiElements.fileInput.files[0];
644
  if (file.type.startsWith('image/')) {
 
672
  title: title
673
  };
674
  headers['Content-Type'] = 'application/json';
 
 
675
  }
676
 
677
  enterChatView();
 
806
  if (uiElements.stopBtn) uiElements.stopBtn.style.pointerEvents = 'auto';
807
  }
808
 
809
+ // Logout handler
810
+ const logoutBtn = document.getElementById('logoutBtn');
811
+ if (logoutBtn) {
812
+ logoutBtn.addEventListener('click', async () => {
813
+ console.log('Logout button clicked');
814
+ try {
815
+ const response = await fetch('/logout', {
816
+ method: 'GET',
817
+ credentials: 'include'
818
+ });
819
+ if (response.ok) {
820
+ localStorage.removeItem('token');
821
+ console.log('Token removed from localStorage');
822
+ window.location.href = '/login';
823
+ } else {
824
+ console.error('Logout failed:', response.status);
825
+ alert('Failed to log out. Please try again.');
826
+ }
827
+ } catch (error) {
828
+ console.error('Logout error:', error);
829
+ alert('Error during logout: ' + error.message);
830
+ }
831
+ });
832
+ }
833
+
834
  // Settings Modal
835
  if (uiElements.settingsBtn) {
836
  uiElements.settingsBtn.addEventListener('click', () => {
 
968
  if (uiElements.audioInput) uiElements.audioInput.addEventListener('change', previewFile);
969
 
970
  if (uiElements.sendBtn) {
971
+ uiElements.sendBtn.addEventListener('click', (e) => {
972
+ e.preventDefault();
973
  if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
974
+ if (uiElements.input.value.trim()) {
975
+ submitMessage();
976
+ }
977
+ });
978
+
979
+ let pressTimer;
980
+ uiElements.sendBtn.addEventListener('mousedown', (e) => {
981
+ if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
982
+ pressTimer = setTimeout(() => {
983
+ startVoiceRecording();
984
+ }, 500);
985
+ });
986
+ uiElements.sendBtn.addEventListener('mouseup', () => {
987
+ clearTimeout(pressTimer);
988
+ if (isRecording) stopVoiceRecording();
989
  });
990
+ uiElements.sendBtn.addEventListener('mouseleave', () => {
991
+ clearTimeout(pressTimer);
992
+ if (isRecording) stopVoiceRecording();
993
+ });
994
+
995
+ uiElements.sendBtn.addEventListener('touchstart', (e) => {
996
  e.preventDefault();
997
  if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
998
+ pressTimer = setTimeout(() => {
999
+ startVoiceRecording();
1000
+ }, 500);
1001
  });
1002
+ uiElements.sendBtn.addEventListener('touchend', (e) => {
1003
  e.preventDefault();
1004
+ clearTimeout(pressTimer);
1005
  if (isRecording) stopVoiceRecording();
1006
  });
1007
+ uiElements.sendBtn.addEventListener('touchcancel', (e) => {
1008
  e.preventDefault();
1009
+ clearTimeout(pressTimer);
1010
  if (isRecording) stopVoiceRecording();
1011
  });
1012
  }
1013
 
1014
  if (uiElements.form) {
1015
+ uiElements.form.addEventListener('submit', (e) => {
1016
  e.preventDefault();
1017
+ if (!isRecording && uiElements.input.value.trim()) {
1018
+ submitMessage();
1019
+ }
1020
  });
1021
  }
1022
 
1023
  if (uiElements.input) {
1024
+ uiElements.input.addEventListener('keydown', (e) => {
1025
  if (e.key === 'Enter' && !e.shiftKey) {
1026
  e.preventDefault();
1027
  if (!isRecording && !uiElements.sendBtn.disabled) submitMessage();
 
1059
  if (uiElements.newConversationBtn) {
1060
  uiElements.newConversationBtn.addEventListener('click', createNewConversation);
1061
  }
1062
+
1063
+ // Debug localStorage
1064
+ const originalRemoveItem = localStorage.removeItem;
1065
+ localStorage.removeItem = function(key) {
1066
+ console.log('Removing from localStorage:', key);
1067
+ originalRemoveItem.apply(this, arguments);
1068
+ };
templates/index.html CHANGED
@@ -4,24 +4,20 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <meta name="description" content="MGZon Chatbot – AI-powered assistant for code generation, web search, and e-commerce solutions by Mark Al-Asfar from Alexandria, Egypt.">
7
- <meta name="keywords" content="MGZon Chatbot, AI assistant, code generation, e-commerce, Mark Al-Asfar,
8
- United States, FastAPI, Gradio">
9
  <meta name="author" content="Mark Al-Asfar">
10
  <meta name="robots" content="index, follow">
11
  <title>MGZon Chatbot – Powered by AI</title>
12
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
13
- <!-- manifest for Android/Chrome -->
14
- <link rel="manifest" href="/static/manifest.json">
15
-
16
- <!-- iOS Web App Support -->
17
- <link rel="apple-touch-icon" sizes="180x180" href="/static/images/icons/mg-180.png">
18
- <meta name="apple-mobile-web-app-capable" content="yes">
19
- <meta name="apple-mobile-web-app-title" content="MGZon">
20
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
21
-
22
- <!-- General Theme -->
23
- <meta name="theme-color" content="#2d3748">
24
-
25
  <!-- Open Graph -->
26
  <meta property="og:title" content="MGZon Chatbot – AI-Powered Solutions">
27
  <meta property="og:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
@@ -34,29 +30,29 @@ United States, FastAPI, Gradio">
34
  <meta name="twitter:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
35
  <meta name="twitter:image" content="/static/images/mg.svg">
36
  <!-- JSON-LD -->
37
- <script type="application/ld+json">
38
- {
39
- "@context": "https://schema.org",
40
- "@type": "WebSite",
41
- "name": "MGZon Chatbot",
42
- "url": "https://mgzon-mgzon-app.hf.space/",
43
- "description": "MGZon Chatbot by Mark Al-Asfar: Your AI assistant for code generation, real-time web search, and e-commerce solutions.",
44
- "keywords": ["MGZon Chatbot", "AI chatbot", "Code generation AI", "E-commerce AI solutions", "Mark Al-Asfar", "AI assistant for developers", "FastAPI chatbot", "Real-time web search AI", "MGZon AI", "Python AI chatbot", "AI for coding", "E-commerce automation", "Hugging Face AI", "2025 AI trends", "chatgpt", "grok", "deepseek", "text generation"],
45
- "potentialAction": {
46
- "@type": "SearchAction",
47
- "target": "https://mgzon-mgzon-app.hf.space/?q={search_term_string}",
48
- "query-input": "required name=search_term_string"
49
- },
50
- "publisher": {
51
- "@type": "Organization",
52
- "name": "MGZon AI",
53
- "logo": {
54
- "@type": "ImageObject",
55
- "url": "/static/images/mg.svg"
 
56
  }
57
  }
58
- }
59
- </script>
60
  <!-- Tailwind (v3) -->
61
  <script src="https://cdn.tailwindcss.com"></script>
62
  <!-- Boxicons -->
@@ -134,8 +130,13 @@ United States, FastAPI, Gradio">
134
  <a href="/" class="px-4 py-2 rounded-lg bg-emerald-600">Home</a>
135
  <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
136
  <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
137
- <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
138
- <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
 
 
 
 
 
139
  <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
140
  <a href="https://mark-elasfar.web.app/" target="_blank" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Mark Al-Asfar</a>
141
  </nav>
@@ -150,17 +151,15 @@ United States, FastAPI, Gradio">
150
  Welcome to MGZon Chatbot 🚀
151
  </h1>
152
  <p class="text-lg max-w-2xl mx-auto mb-8">
153
- MGZon Chatbot is an AI-powered assistant for code generation, web search, and e-commerce solutions. Built with Gradio and FastAPI by Mark Al-Asfar from
154
- United States. Ready to code smarter and shop better?
155
- Discover MGZon Chatbot, an AI-powered assistant by Mark Al-Asfar for code generation, real-time web search, and e-commerce automation. Built with FastAPI and Hugging Face AI, MGZon rivals tools like ChatGPT, Grok, and DeepSeek.
156
  </p>
157
- {% if user %}
158
  <div class="flex justify-center gap-4">
159
  <a href="/chat" id="launchBtn" class="inline-flex items-center bg-gradient-to-r from-emerald-500 to-teal-600 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition-transform shadow-lg hover:shadow-xl">
160
  Launch Chatbot <i class="bx bx-rocket ml-2"></i>
161
  <span id="spinner" class="loading hidden"></span>
162
  </a>
163
- <a href="/auth/jwt/logout" class="inline-flex items-center bg-gradient-to-r from-red-500 to-orange-600 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition-transform shadow-lg hover:shadow-xl">
164
  Logout <i class="bx bx-log-out ml-2"></i>
165
  </a>
166
  </div>
@@ -246,7 +245,7 @@ United States. Ready to code smarter and shop better?
246
  <!-- Footer -->
247
  <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
248
  <div class="container max-w-6xl mx-auto text-center">
249
- <img src="/static/images/mg.svg" alt="MGZon Chatbot Logo by Mark Al-Asfar" class="w-32 h-32 mx-auto mb-6 animate-bounce">
250
  <p class="mb-4">
251
  Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
252
  | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
@@ -314,75 +313,132 @@ United States. Ready to code smarter and shop better?
314
  </div>
315
  <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
316
  </div>
317
- <button id="installAppBtn" style="display: none;" class="fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded shadow-lg z-50">
318
  📲 Install MG Chat
319
  </button>
320
  </footer>
321
  <script>
 
322
  const sidebar = document.getElementById('sidebar');
323
  const toggleBtn = document.getElementById('sidebarToggle');
324
  const closeSidebarBtn = document.getElementById('closeSidebar');
325
- toggleBtn.addEventListener('click', () => {
326
- sidebar.classList.toggle('active');
327
- sidebar.classList.toggle('-translate-x-full');
328
- sidebar.classList.toggle('translate-x-0');
329
- });
330
- closeSidebarBtn.addEventListener('click', () => {
331
- sidebar.classList.remove('active');
332
- sidebar.classList.add('-translate-x-full');
333
- sidebar.classList.remove('translate-x-0');
334
- });
 
 
 
 
 
 
 
 
 
 
 
 
335
  document.addEventListener('click', (e) => {
336
  if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
337
  sidebar.classList.remove('active');
338
  sidebar.classList.add('-translate-x-full');
339
  sidebar.classList.remove('translate-x-0');
 
340
  }
341
  });
 
 
342
  function showCardDetails(cardId) {
343
  document.getElementById(`${cardId}-details`).classList.remove('hidden');
344
  }
 
345
  function closeCardDetails(cardId) {
346
  document.getElementById(`${cardId}-details`).classList.add('hidden');
347
  }
 
 
348
  const launchBtn = document.getElementById('launchBtn');
349
  const spinner = document.getElementById('spinner');
350
- launchBtn.addEventListener('click', (e) => {
351
- spinner.classList.remove('hidden');
352
- setTimeout(() => spinner.classList.add('hidden'), 2000);
353
- });
354
-
355
- if ('serviceWorker' in navigator) {
356
- navigator.serviceWorker.register('/static/js/sw.js')
357
- .then(function(reg) {
358
- console.log('✅ Service Worker Registered', reg);
359
- }).catch(function(err) {
360
- console.error('❌ Service Worker registration failed', err);
361
  });
362
- }
363
-
364
- // التعامل مع حدث beforeinstallprompt لتثبيت التطبيق
365
- let deferredPrompt;
366
- window.addEventListener('beforeinstallprompt', (e) => {
367
- e.preventDefault();
368
- deferredPrompt = e;
369
- const installBtn = document.getElementById('installAppBtn');
370
- if (installBtn) {
371
- installBtn.style.display = 'block';
372
 
373
- installBtn.addEventListener('click', () => {
374
- deferredPrompt.prompt();
375
- deferredPrompt.userChoice.then(choice => {
376
- if (choice.outcome === 'accepted') {
377
- console.log('✅ User accepted the install prompt');
 
 
 
 
 
 
 
 
 
 
378
  } else {
379
- console.log(' User dismissed the install prompt');
 
380
  }
381
- deferredPrompt = null;
382
- });
 
 
383
  });
384
  }
385
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  </script>
387
  </body>
388
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <meta name="description" content="MGZon Chatbot – AI-powered assistant for code generation, web search, and e-commerce solutions by Mark Al-Asfar from Alexandria, Egypt.">
7
+ <meta name="keywords" content="MGZon Chatbot, AI assistant, code generation, e-commerce, Mark Al-Asfar, United States, FastAPI, Gradio">
 
8
  <meta name="author" content="Mark Al-Asfar">
9
  <meta name="robots" content="index, follow">
10
  <title>MGZon Chatbot – Powered by AI</title>
11
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <!-- manifest for Android/Chrome -->
13
+ <link rel="manifest" href="/static/manifest.json">
14
+ <!-- iOS Web App Support -->
15
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/images/mg-180.png">
16
+ <meta name="apple-mobile-web-app-capable" content="yes">
17
+ <meta name="apple-mobile-web-app-title" content="MGZon">
18
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
19
+ <!-- General Theme -->
20
+ <meta name="theme-color" content="#2d3748">
 
 
 
21
  <!-- Open Graph -->
22
  <meta property="og:title" content="MGZon Chatbot – AI-Powered Solutions">
23
  <meta property="og:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
 
30
  <meta name="twitter:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
31
  <meta name="twitter:image" content="/static/images/mg.svg">
32
  <!-- JSON-LD -->
33
+ <script type="application/ld+json">
34
+ {
35
+ "@context": "https://schema.org",
36
+ "@type": "WebSite",
37
+ "name": "MGZon Chatbot",
38
+ "url": "https://mgzon-mgzon-app.hf.space/",
39
+ "description": "MGZon Chatbot by Mark Al-Asfar: Your AI assistant for code generation, real-time web search, and e-commerce solutions.",
40
+ "keywords": ["MGZon Chatbot", "AI chatbot", "Code generation AI", "E-commerce AI solutions", "Mark Al-Asfar", "AI assistant for developers", "FastAPI chatbot", "Real-time web search AI", "MGZon AI", "Python AI chatbot", "AI for coding", "E-commerce automation", "Hugging Face AI", "2025 AI trends", "chatgpt", "grok", "deepseek", "text generation"],
41
+ "potentialAction": {
42
+ "@type": "SearchAction",
43
+ "target": "https://mgzon-mgzon-app.hf.space/?q={search_term_string}",
44
+ "query-input": "required name=search_term_string"
45
+ },
46
+ "publisher": {
47
+ "@type": "Organization",
48
+ "name": "MGZon AI",
49
+ "logo": {
50
+ "@type": "ImageObject",
51
+ "url": "/static/images/mg.svg"
52
+ }
53
  }
54
  }
55
+ </script>
 
56
  <!-- Tailwind (v3) -->
57
  <script src="https://cdn.tailwindcss.com"></script>
58
  <!-- Boxicons -->
 
130
  <a href="/" class="px-4 py-2 rounded-lg bg-emerald-600">Home</a>
131
  <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
132
  <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
133
+ {% if is_authenticated %}
134
+ <a href="/chat" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Chat</a>
135
+ <a id="logoutBtn" href="/logout" class="px-4 py-2 rounded-lg hover:bg-red-600 transition">Logout</a>
136
+ {% else %}
137
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
138
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
139
+ {% endif %}
140
  <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
141
  <a href="https://mark-elasfar.web.app/" target="_blank" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Mark Al-Asfar</a>
142
  </nav>
 
151
  Welcome to MGZon Chatbot 🚀
152
  </h1>
153
  <p class="text-lg max-w-2xl mx-auto mb-8">
154
+ MGZon Chatbot is an AI-powered assistant for code generation, web search, and e-commerce solutions. Built with Gradio and FastAPI by Mark Al-Asfar from United States. Ready to code smarter and shop better? Discover MGZon Chatbot, an AI-powered assistant by Mark Al-Asfar for code generation, real-time web search, and e-commerce automation. Built with FastAPI and Hugging Face AI, MGZon rivals tools like ChatGPT, Grok, and DeepSeek.
 
 
155
  </p>
156
+ {% if is_authenticated %}
157
  <div class="flex justify-center gap-4">
158
  <a href="/chat" id="launchBtn" class="inline-flex items-center bg-gradient-to-r from-emerald-500 to-teal-600 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition-transform shadow-lg hover:shadow-xl">
159
  Launch Chatbot <i class="bx bx-rocket ml-2"></i>
160
  <span id="spinner" class="loading hidden"></span>
161
  </a>
162
+ <a id="logoutBtn" href="/logout" class="inline-flex items-center bg-gradient-to-r from-red-500 to-orange-600 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition-transform shadow-lg hover:shadow-xl">
163
  Logout <i class="bx bx-log-out ml-2"></i>
164
  </a>
165
  </div>
 
245
  <!-- Footer -->
246
  <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
247
  <div class="container max-w-6xl mx-auto text-center">
248
+ <img src="/static/images/mg.svg" alt="MGZon Chatbot Logo by Mark Al-Asfar" class="w-32 h-32 mx-auto mb-6 animate-bounce">
249
  <p class="mb-4">
250
  Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
251
  | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
 
313
  </div>
314
  <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
315
  </div>
316
+ <button id="installAppBtn" style="display: none;" class="fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded shadow-lg z-50">
317
  📲 Install MG Chat
318
  </button>
319
  </footer>
320
  <script>
321
+ // Sidebar toggle
322
  const sidebar = document.getElementById('sidebar');
323
  const toggleBtn = document.getElementById('sidebarToggle');
324
  const closeSidebarBtn = document.getElementById('closeSidebar');
325
+
326
+ if (toggleBtn && sidebar) {
327
+ toggleBtn.addEventListener('click', () => {
328
+ sidebar.classList.toggle('active');
329
+ sidebar.classList.toggle('-translate-x-full');
330
+ sidebar.classList.toggle('translate-x-0');
331
+ console.log('Sidebar toggled');
332
+ });
333
+ } else {
334
+ console.warn('Sidebar or toggle button not found');
335
+ }
336
+
337
+ if (closeSidebarBtn && sidebar) {
338
+ closeSidebarBtn.addEventListener('click', () => {
339
+ sidebar.classList.remove('active');
340
+ sidebar.classList.add('-translate-x-full');
341
+ sidebar.classList.remove('translate-x-0');
342
+ console.log('Sidebar closed');
343
+ });
344
+ }
345
+
346
+ // Close sidebar when clicking outside on mobile
347
  document.addEventListener('click', (e) => {
348
  if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
349
  sidebar.classList.remove('active');
350
  sidebar.classList.add('-translate-x-full');
351
  sidebar.classList.remove('translate-x-0');
352
+ console.log('Sidebar closed due to outside click');
353
  }
354
  });
355
+
356
+ // Show/hide card details
357
  function showCardDetails(cardId) {
358
  document.getElementById(`${cardId}-details`).classList.remove('hidden');
359
  }
360
+
361
  function closeCardDetails(cardId) {
362
  document.getElementById(`${cardId}-details`).classList.add('hidden');
363
  }
364
+
365
+ // Launch button spinner
366
  const launchBtn = document.getElementById('launchBtn');
367
  const spinner = document.getElementById('spinner');
368
+ if (launchBtn && spinner) {
369
+ launchBtn.addEventListener('click', (e) => {
370
+ spinner.classList.remove('hidden');
371
+ setTimeout(() => spinner.classList.add('hidden'), 2000);
 
 
 
 
 
 
 
372
  });
373
+ }
 
 
 
 
 
 
 
 
 
374
 
375
+ // Logout handler
376
+ const logoutBtn = document.getElementById('logoutBtn');
377
+ if (logoutBtn) {
378
+ logoutBtn.addEventListener('click', async (e) => {
379
+ e.preventDefault();
380
+ console.log('Logout button clicked');
381
+ try {
382
+ const response = await fetch('/logout', {
383
+ method: 'GET',
384
+ credentials: 'include'
385
+ });
386
+ if (response.ok) {
387
+ localStorage.removeItem('token');
388
+ console.log('Token removed from localStorage');
389
+ window.location.href = '/login';
390
  } else {
391
+ console.error('Logout failed:', response.status);
392
+ alert('Failed to log out. Please try again.');
393
  }
394
+ } catch (error) {
395
+ console.error('Logout error:', error);
396
+ alert('Error during logout: ' + error.message);
397
+ }
398
  });
399
  }
400
+
401
+ // Service Worker for PWA
402
+ if ('serviceWorker' in navigator) {
403
+ navigator.serviceWorker.register('/static/js/sw.js')
404
+ .then(function(reg) {
405
+ console.log('✅ Service Worker Registered', reg);
406
+ }).catch(function(err) {
407
+ console.error('❌ Service Worker registration failed', err);
408
+ });
409
+ }
410
+
411
+ // Handle beforeinstallprompt for PWA
412
+ let deferredPrompt;
413
+ window.addEventListener('beforeinstallprompt', (e) => {
414
+ e.preventDefault();
415
+ deferredPrompt = e;
416
+ const installBtn = document.getElementById('installAppBtn');
417
+ if (installBtn) {
418
+ installBtn.style.display = 'block';
419
+ installBtn.addEventListener('click', () => {
420
+ deferredPrompt.prompt();
421
+ deferredPrompt.userChoice.then(choice => {
422
+ if (choice.outcome === 'accepted') {
423
+ console.log('✅ User accepted the install prompt');
424
+ } else {
425
+ console.log('❌ User dismissed the install prompt');
426
+ }
427
+ deferredPrompt = null;
428
+ });
429
+ });
430
+ }
431
+ });
432
+
433
+ // Card animations
434
+ document.querySelectorAll('.glass').forEach(card => {
435
+ card.addEventListener('mouseenter', () => {
436
+ card.style.transform = 'scale(1.05) rotate(1deg)';
437
+ });
438
+ card.addEventListener('mouseleave', () => {
439
+ card.style.transform = 'scale(1) rotate(0deg)';
440
+ });
441
+ });
442
  </script>
443
  </body>
444
  </html>