ibrahimlasfar commited on
Commit
87b09dd
·
1 Parent(s): 6ac883d

Full Update

Browse files
Files changed (47) hide show
  1. api/auth.py +2 -8
  2. api/endpoints.py +437 -162
  3. api/models.py +84 -4
  4. main.py +133 -50
  5. static/css/sidebar.css +151 -0
  6. static/images/generate_icons.py +14 -0
  7. static/images/ic/404.html +31 -0
  8. static/images/ic/500.html +32 -0
  9. static/images/ic/about.html +279 -0
  10. static/images/ic/blog.html +237 -0
  11. static/images/ic/blog_post.html +263 -0
  12. static/images/ic/chat.html +294 -0
  13. static/images/ic/docs.html +284 -0
  14. static/images/ic/index.html +342 -0
  15. static/images/ic/login.html +218 -0
  16. static/images/ic/mg-128.png +0 -0
  17. static/images/ic/mg-192.png +0 -0
  18. static/images/ic/mg-256.png +0 -0
  19. static/images/ic/mg-384.png +0 -0
  20. static/images/ic/mg-48.png +0 -0
  21. static/images/ic/mg-512.png +0 -0
  22. static/images/ic/mg-72.png +0 -0
  23. static/images/ic/mg-96.png +0 -0
  24. static/images/ic/mg.svg +1 -0
  25. static/images/ic/register.html +214 -0
  26. static/images/icons/mg-128.png +0 -0
  27. static/images/icons/mg-192.png +0 -0
  28. static/images/icons/mg-256.png +0 -0
  29. static/images/icons/mg-384.png +0 -0
  30. static/images/icons/mg-48.png +0 -0
  31. static/images/icons/mg-512.png +0 -0
  32. static/images/icons/mg-72.png +0 -0
  33. static/images/icons/mg-96.png +0 -0
  34. static/images/mg.png +0 -0
  35. static/images/mg.svg +110 -1
  36. static/images/mgz.svg +1 -0
  37. static/images/swipe-hint.svg +8 -0
  38. static/js/chat.js +694 -447
  39. static/js/sw.js +44 -0
  40. static/manifest.json +57 -0
  41. templates/500.html +13 -0
  42. templates/blog_post.html +2 -0
  43. templates/chat.html +314 -151
  44. templates/index.html +47 -1
  45. templates/login.html +50 -3
  46. templates/register.html +10 -2
  47. utils/generation.py +26 -6
api/auth.py CHANGED
@@ -1,4 +1,3 @@
1
- # api/auth.py
2
  from fastapi_users import FastAPIUsers
3
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
4
  from fastapi_users.db import SQLAlchemyUserDatabase
@@ -8,11 +7,9 @@ from api.database import SessionLocal
8
  from api.models import User, OAuthAccount
9
  import os
10
 
11
- # إعداد Cookie Transport
12
  cookie_transport = CookieTransport(cookie_max_age=3600)
13
 
14
- # إعداد JWT Strategy
15
- SECRET = os.getenv("JWT_SECRET", "your_jwt_secret_here") # ضعه في Space Secrets
16
 
17
  def get_jwt_strategy() -> JWTStrategy:
18
  return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
@@ -23,7 +20,6 @@ auth_backend = AuthenticationBackend(
23
  get_strategy=get_jwt_strategy,
24
  )
25
 
26
- # إعداد عملاء OAuth
27
  GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
28
  GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
29
  GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
@@ -32,11 +28,9 @@ GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
32
  google_oauth_client = GoogleOAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
33
  github_oauth_client = GitHubOAuth2(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET)
34
 
35
- # إعداد FastAPIUsers
36
  fastapi_users = FastAPIUsers[User, int](
37
  lambda: SQLAlchemyUserDatabase(User, SessionLocal(), oauth_account_table=OAuthAccount),
38
  [auth_backend],
39
  )
40
 
41
- # Dependency للحصول على المستخدم الحالي (اختياري)
42
- current_active_user = fastapi_users.current_user(active=True, optional=True) # ← تغيير هنا
 
 
1
  from fastapi_users import FastAPIUsers
2
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
3
  from fastapi_users.db import SQLAlchemyUserDatabase
 
7
  from api.models import User, OAuthAccount
8
  import os
9
 
 
10
  cookie_transport = CookieTransport(cookie_max_age=3600)
11
 
12
+ SECRET = os.getenv("JWT_SECRET")
 
13
 
14
  def get_jwt_strategy() -> JWTStrategy:
15
  return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
 
20
  get_strategy=get_jwt_strategy,
21
  )
22
 
 
23
  GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
24
  GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
25
  GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
 
28
  google_oauth_client = GoogleOAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
29
  github_oauth_client = GitHubOAuth2(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET)
30
 
 
31
  fastapi_users = FastAPIUsers[User, int](
32
  lambda: SQLAlchemyUserDatabase(User, SessionLocal(), oauth_account_table=OAuthAccount),
33
  [auth_backend],
34
  )
35
 
36
+ current_active_user = fastapi_users.current_user(active=True, optional=True)
 
api/endpoints.py CHANGED
@@ -1,49 +1,121 @@
1
- # api/endpoints.py
2
  import os
3
  import uuid
4
  from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
5
  from fastapi.responses import StreamingResponse
6
- from api.models import QueryRequest, User
7
  from api.auth import current_active_user
8
  from api.database import get_db
9
  from sqlalchemy.orm import Session
10
  from utils.generation import request_generation, select_model
 
11
  import io
12
  from openai import OpenAI
13
  from motor.motor_asyncio import AsyncIOMotorClient
 
 
 
14
 
15
  router = APIRouter()
 
16
 
 
17
  HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
18
  BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
 
 
 
19
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
20
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
21
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b:together")
22
- SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2:featherless-ai")
23
- TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-20b:together")
24
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
25
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
26
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
27
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
28
 
29
- # إعداد MongoDB
 
 
 
 
 
 
 
 
 
 
 
30
  MONGO_URI = os.getenv("MONGODB_URI")
31
- if not MONGO_URI:
32
- raise ValueError("MONGODB_URI is not set in environment variables.")
33
  client = AsyncIOMotorClient(MONGO_URI)
34
  db = client["hager"]
35
  session_message_counts = db["session_message_counts"]
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  @router.get("/api/model-info")
38
  async def model_info():
39
  return {
40
- "model_name": MODEL_NAME,
41
- "secondary_model": SECONDARY_MODEL_NAME,
42
- "tertiary_model": TERTIARY_MODEL_NAME,
43
- "clip_base_model": CLIP_BASE_MODEL,
44
- "clip_large_model": CLIP_LARGE_MODEL,
45
- "asr_model": ASR_MODEL,
46
- "tts_model": TTS_MODEL,
 
 
47
  "api_base": API_ENDPOINT,
48
  "fallback_api_base": FALLBACK_API_ENDPOINT,
49
  "status": "online"
@@ -64,46 +136,77 @@ async def chat_endpoint(
64
  user: User = Depends(current_active_user),
65
  db: Session = Depends(get_db)
66
  ):
67
- session_id = request.session.get("session_id")
68
- if not user and not session_id:
69
- session_id = str(uuid.uuid4())
70
- request.session["session_id"] = session_id
71
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
72
-
73
  if not user:
74
- session_doc = await session_message_counts.find_one({"session_id": session_id})
75
- if not session_doc:
76
- session_doc = {"session_id": session_id, "message_count": 0}
77
- await session_message_counts.insert_one(session_doc)
78
- message_count = session_doc["message_count"] + 1
79
- await session_message_counts.update_one(
80
- {"session_id": session_id},
81
- {"$set": {"message_count": message_count}}
82
- )
83
- if message_count > 4:
84
- raise HTTPException(
85
- status_code=status.HTTP_403_FORBIDDEN,
86
- detail="Message limit reached. Please log in to continue."
87
  )
88
-
89
- model_name, api_endpoint = select_model(req.message, input_type="text")
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  stream = request_generation(
91
  api_key=HF_TOKEN,
92
  api_base=api_endpoint,
93
  message=req.message,
94
- system_prompt=req.system_prompt,
95
  model_name=model_name,
96
  chat_history=req.history,
97
  temperature=req.temperature,
98
- max_new_tokens=req.max_new_tokens,
99
  deep_search=req.enable_browsing,
100
  input_type="text",
101
  output_format=req.output_format
102
  )
103
  if req.output_format == "audio":
104
- audio_data = b"".join([chunk for chunk in stream if isinstance(chunk, bytes)])
 
 
 
 
105
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
106
- response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  return {"response": response}
108
 
109
  @router.post("/api/audio-transcription")
@@ -113,28 +216,30 @@ async def audio_transcription_endpoint(
113
  user: User = Depends(current_active_user),
114
  db: Session = Depends(get_db)
115
  ):
116
- session_id = request.session.get("session_id")
117
- if not user and not session_id:
118
- session_id = str(uuid.uuid4())
119
- request.session["session_id"] = session_id
120
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
121
-
122
  if not user:
123
- session_doc = await session_message_counts.find_one({"session_id": session_id})
124
- if not session_doc:
125
- session_doc = {"session_id": session_id, "message_count": 0}
126
- await session_message_counts.insert_one(session_doc)
127
- message_count = session_doc["message_count"] + 1
128
- await session_message_counts.update_one(
129
- {"session_id": session_id},
130
- {"$set": {"message_count": message_count}}
131
- )
132
- if message_count > 4:
133
- raise HTTPException(
134
- status_code=status.HTTP_403_FORBIDDEN,
135
- detail="Message limit reached. Please log in to continue."
136
  )
137
-
 
 
 
 
 
 
 
138
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
139
  audio_data = await file.read()
140
  stream = request_generation(
@@ -144,12 +249,31 @@ async def audio_transcription_endpoint(
144
  system_prompt="Transcribe the provided audio using Whisper.",
145
  model_name=model_name,
146
  temperature=0.7,
147
- max_new_tokens=128000,
148
  input_type="audio",
149
  audio_data=audio_data,
150
  output_format="text"
151
  )
152
- response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  return {"transcription": response}
154
 
155
  @router.post("/api/text-to-speech")
@@ -159,28 +283,9 @@ async def text_to_speech_endpoint(
159
  user: User = Depends(current_active_user),
160
  db: Session = Depends(get_db)
161
  ):
162
- session_id = request.session.get("session_id")
163
- if not user and not session_id:
164
- session_id = str(uuid.uuid4())
165
- request.session["session_id"] = session_id
166
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
167
-
168
  if not user:
169
- session_doc = await session_message_counts.find_one({"session_id": session_id})
170
- if not session_doc:
171
- session_doc = {"session_id": session_id, "message_count": 0}
172
- await session_message_counts.insert_one(session_doc)
173
- message_count = session_doc["message_count"] + 1
174
- await session_message_counts.update_one(
175
- {"session_id": session_id},
176
- {"$set": {"message_count": message_count}}
177
- )
178
- if message_count > 4:
179
- raise HTTPException(
180
- status_code=status.HTTP_403_FORBIDDEN,
181
- detail="Message limit reached. Please log in to continue."
182
- )
183
-
184
  text = req.get("text", "")
185
  model_name, api_endpoint = select_model("text to speech", input_type="tts")
186
  stream = request_generation(
@@ -190,11 +295,15 @@ async def text_to_speech_endpoint(
190
  system_prompt="Convert the provided text to speech using a text-to-speech model.",
191
  model_name=model_name,
192
  temperature=0.7,
193
- max_new_tokens=128000,
194
  input_type="tts",
195
  output_format="audio"
196
  )
197
- audio_data = b"".join([chunk for chunk in stream if isinstance(chunk, bytes)])
 
 
 
 
198
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
199
 
200
  @router.post("/api/code")
@@ -204,49 +313,43 @@ async def code_endpoint(
204
  user: User = Depends(current_active_user),
205
  db: Session = Depends(get_db)
206
  ):
207
- session_id = request.session.get("session_id")
208
- if not user and not session_id:
209
- session_id = str(uuid.uuid4())
210
- request.session["session_id"] = session_id
211
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
212
-
213
  if not user:
214
- session_doc = await session_message_counts.find_one({"session_id": session_id})
215
- if not session_doc:
216
- session_doc = {"session_id": session_id, "message_count": 0}
217
- await session_message_counts.insert_one(session_doc)
218
- message_count = session_doc["message_count"] + 1
219
- await session_message_counts.update_one(
220
- {"session_id": session_id},
221
- {"$set": {"message_count": message_count}}
222
- )
223
- if message_count > 4:
224
- raise HTTPException(
225
- status_code=status.HTTP_403_FORBIDDEN,
226
- detail="Message limit reached. Please log in to continue."
227
- )
228
-
229
  framework = req.get("framework")
230
  task = req.get("task")
231
  code = req.get("code", "")
232
  output_format = req.get("output_format", "text")
233
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
234
- model_name, api_endpoint = select_model(prompt, input_type="text")
 
 
 
 
 
235
  stream = request_generation(
236
  api_key=HF_TOKEN,
237
  api_base=api_endpoint,
238
  message=prompt,
239
- system_prompt="You are a coding expert. Provide detailed, well-commented code with examples and explanations.",
240
  model_name=model_name,
241
  temperature=0.7,
242
- max_new_tokens=128000,
243
  input_type="text",
244
  output_format=output_format
245
  )
246
  if output_format == "audio":
247
- audio_data = b"".join([chunk for chunk in stream if isinstance(chunk, bytes)])
 
 
 
 
248
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
249
- response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
 
 
 
 
250
  return {"generated_code": response}
251
 
252
  @router.post("/api/analysis")
@@ -256,46 +359,40 @@ async def analysis_endpoint(
256
  user: User = Depends(current_active_user),
257
  db: Session = Depends(get_db)
258
  ):
259
- session_id = request.session.get("session_id")
260
- if not user and not session_id:
261
- session_id = str(uuid.uuid4())
262
- request.session["session_id"] = session_id
263
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
264
-
265
  if not user:
266
- session_doc = await session_message_counts.find_one({"session_id": session_id})
267
- if not session_doc:
268
- session_doc = {"session_id": session_id, "message_count": 0}
269
- await session_message_counts.insert_one(session_doc)
270
- message_count = session_doc["message_count"] + 1
271
- await session_message_counts.update_one(
272
- {"session_id": session_id},
273
- {"$set": {"message_count": message_count}}
274
- )
275
- if message_count > 4:
276
- raise HTTPException(
277
- status_code=status.HTTP_403_FORBIDDEN,
278
- detail="Message limit reached. Please log in to continue."
279
- )
280
-
281
  message = req.get("text", "")
282
  output_format = req.get("output_format", "text")
283
- model_name, api_endpoint = select_model(message, input_type="text")
 
 
 
 
 
284
  stream = request_generation(
285
  api_key=HF_TOKEN,
286
  api_base=api_endpoint,
287
  message=message,
288
- system_prompt="You are an expert analyst. Provide detailed analysis with step-by-step reasoning and examples.",
289
  model_name=model_name,
290
  temperature=0.7,
291
- max_new_tokens=128000,
292
  input_type="text",
293
  output_format=output_format
294
  )
295
  if output_format == "audio":
296
- audio_data = b"".join([chunk for chunk in stream if isinstance(chunk, bytes)])
 
 
 
 
297
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
298
- response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
 
 
 
 
299
  return {"analysis": response}
300
 
301
  @router.post("/api/image-analysis")
@@ -306,46 +403,73 @@ async def image_analysis_endpoint(
306
  user: User = Depends(current_active_user),
307
  db: Session = Depends(get_db)
308
  ):
309
- session_id = request.session.get("session_id")
310
- if not user and not session_id:
311
- session_id = str(uuid.uuid4())
312
- request.session["session_id"] = session_id
313
- await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
314
-
315
  if not user:
316
- session_doc = await session_message_counts.find_one({"session_id": session_id})
317
- if not session_doc:
318
- session_doc = {"session_id": session_id, "message_count": 0}
319
- await session_message_counts.insert_one(session_doc)
320
- message_count = session_doc["message_count"] + 1
321
- await session_message_counts.update_one(
322
- {"session_id": session_id},
323
- {"$set": {"message_count": message_count}}
324
- )
325
- if message_count > 4:
326
- raise HTTPException(
327
- status_code=status.HTTP_403_FORBIDDEN,
328
- detail="Message limit reached. Please log in to continue."
329
  )
330
-
331
- model_name, api_endpoint = select_model("analyze image", input_type="image")
 
 
 
 
 
 
 
 
332
  image_data = await file.read()
 
 
 
 
333
  stream = request_generation(
334
  api_key=HF_TOKEN,
335
  api_base=api_endpoint,
336
  message="Analyze this image",
337
- system_prompt="You are an expert in image analysis. Provide detailed descriptions or classifications based on the query.",
338
  model_name=model_name,
339
  temperature=0.7,
340
- max_new_tokens=128000,
341
  input_type="image",
342
  image_data=image_data,
343
  output_format=output_format
344
  )
345
  if output_format == "audio":
346
- audio_data = b"".join([chunk for chunk in stream if isinstance(chunk, bytes)])
 
 
 
 
347
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
348
- response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  return {"image_analysis": response}
350
 
351
  @router.get("/api/test-model")
@@ -360,3 +484,154 @@ async def test_model(model: str = MODEL_NAME, endpoint: str = API_ENDPOINT):
360
  return {"status": "success", "response": response.choices[0].message.content}
361
  except Exception as e:
362
  return {"status": "error", "message": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import uuid
3
  from fastapi import APIRouter, Depends, HTTPException, Request, status, UploadFile, File
4
  from fastapi.responses import StreamingResponse
5
+ from api.models import QueryRequest, User, Conversation, Message, ConversationOut, ConversationCreate, UserUpdate
6
  from api.auth import current_active_user
7
  from api.database import get_db
8
  from sqlalchemy.orm import Session
9
  from utils.generation import request_generation, select_model
10
+ from utils.web_search import web_search
11
  import io
12
  from openai import OpenAI
13
  from motor.motor_asyncio import AsyncIOMotorClient
14
+ from datetime import datetime
15
+ import logging
16
+ from typing import List
17
 
18
  router = APIRouter()
19
+ logger = logging.getLogger(__name__)
20
 
21
+ # Check HF_TOKEN and BACKUP_HF_TOKEN
22
  HF_TOKEN = os.getenv("HF_TOKEN")
23
+ if not HF_TOKEN:
24
+ logger.error("HF_TOKEN is not set in environment variables.")
25
+ raise ValueError("HF_TOKEN is required for Inference API.")
26
+
27
  BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
28
+ if not BACKUP_HF_TOKEN:
29
+ logger.warning("BACKUP_HF_TOKEN is not set. Fallback to secondary model will not work if primary token fails.")
30
+
31
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
32
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
33
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b")
34
+ SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2")
35
+ TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-20b")
36
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
37
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
38
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
39
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
40
 
41
+ # Model alias mapping for user-friendly names
42
+ MODEL_ALIASES = {
43
+ "advanced": MODEL_NAME,
44
+ "standard": SECONDARY_MODEL_NAME,
45
+ "light": TERTIARY_MODEL_NAME,
46
+ "image_base": CLIP_BASE_MODEL,
47
+ "image_advanced": CLIP_LARGE_MODEL,
48
+ "audio": ASR_MODEL,
49
+ "tts": TTS_MODEL
50
+ }
51
+
52
+ # MongoDB setup
53
  MONGO_URI = os.getenv("MONGODB_URI")
 
 
54
  client = AsyncIOMotorClient(MONGO_URI)
55
  db = client["hager"]
56
  session_message_counts = db["session_message_counts"]
57
 
58
+ # Helper function to handle sessions for non-logged-in users
59
+ async def handle_session(request: Request):
60
+ if not hasattr(request, "session"):
61
+ raise HTTPException(status_code=500, detail="Session middleware not configured")
62
+ session_id = request.session.get("session_id")
63
+ if not session_id:
64
+ session_id = str(uuid.uuid4())
65
+ request.session["session_id"] = session_id
66
+ await session_message_counts.insert_one({"session_id": session_id, "message_count": 0})
67
+
68
+ session_doc = await session_message_counts.find_one({"session_id": session_id})
69
+ if not session_doc:
70
+ session_doc = {"session_id": session_id, "message_count": 0}
71
+ await session_message_counts.insert_one(session_doc)
72
+
73
+ message_count = session_doc["message_count"] + 1
74
+ await session_message_counts.update_one(
75
+ {"session_id": session_id},
76
+ {"$set": {"message_count": message_count}}
77
+ )
78
+ if message_count > 4:
79
+ raise HTTPException(
80
+ status_code=status.HTTP_403_FORBIDDEN,
81
+ detail="Message limit reached. Please log in to continue."
82
+ )
83
+ return session_id
84
+
85
+ @router.get("/api/settings")
86
+ async def get_settings(user: User = Depends(current_active_user)):
87
+ if not user:
88
+ raise HTTPException(status_code=401, detail="Login required")
89
+ return {
90
+ "available_models": [
91
+ {"alias": "advanced", "description": "High-performance model for complex queries"},
92
+ {"alias": "standard", "description": "Balanced model for general use"},
93
+ {"alias": "light", "description": "Lightweight model for quick responses"}
94
+ ],
95
+ "conversation_styles": ["default", "concise", "analytical", "creative"],
96
+ "user_settings": {
97
+ "display_name": user.display_name,
98
+ "preferred_model": user.preferred_model,
99
+ "job_title": user.job_title,
100
+ "education": user.education,
101
+ "interests": user.interests,
102
+ "additional_info": user.additional_info,
103
+ "conversation_style": user.conversation_style
104
+ }
105
+ }
106
+
107
  @router.get("/api/model-info")
108
  async def model_info():
109
  return {
110
+ "available_models": [
111
+ {"alias": "advanced", "description": "High-performance model for complex queries"},
112
+ {"alias": "standard", "description": "Balanced model for general use"},
113
+ {"alias": "light", "description": "Lightweight model for quick responses"},
114
+ {"alias": "image_base", "description": "Basic image analysis model"},
115
+ {"alias": "image_advanced", "description": "Advanced image analysis model"},
116
+ {"alias": "audio", "description": "Audio transcription model (default)"},
117
+ {"alias": "tts", "description": "Text-to-speech model (default)"}
118
+ ],
119
  "api_base": API_ENDPOINT,
120
  "fallback_api_base": FALLBACK_API_ENDPOINT,
121
  "status": "online"
 
136
  user: User = Depends(current_active_user),
137
  db: Session = Depends(get_db)
138
  ):
139
+ logger.info(f"Received chat request: {req}")
140
+
 
 
 
 
141
  if not user:
142
+ await handle_session(request)
143
+
144
+ conversation = None
145
+ if user:
146
+ title = req.title or (req.message[:50] + "..." if len(req.message) > 50 else req.message or "Untitled Conversation")
147
+ conversation = db.query(Conversation).filter(Conversation.user_id == user.id).order_by(Conversation.updated_at.desc()).first()
148
+ if not conversation:
149
+ conversation_id = str(uuid.uuid4())
150
+ conversation = Conversation(
151
+ conversation_id=conversation_id,
152
+ user_id=user.id,
153
+ title=title
 
154
  )
155
+ db.add(conversation)
156
+ db.commit()
157
+ db.refresh(conversation)
158
+
159
+ user_msg = Message(role="user", content=req.message, conversation_id=conversation.id)
160
+ db.add(user_msg)
161
+ db.commit()
162
+
163
+ # Use user's preferred model if set
164
+ preferred_model = user.preferred_model if user else None
165
+ model_name, api_endpoint = select_model(req.message, input_type="text", preferred_model=preferred_model)
166
+ system_prompt = req.system_prompt
167
+ if user and user.additional_info:
168
+ system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
169
+
170
  stream = request_generation(
171
  api_key=HF_TOKEN,
172
  api_base=api_endpoint,
173
  message=req.message,
174
+ system_prompt=system_prompt,
175
  model_name=model_name,
176
  chat_history=req.history,
177
  temperature=req.temperature,
178
+ max_new_tokens=req.max_new_tokens or 2048,
179
  deep_search=req.enable_browsing,
180
  input_type="text",
181
  output_format=req.output_format
182
  )
183
  if req.output_format == "audio":
184
+ audio_chunks = []
185
+ async for chunk in stream:
186
+ if isinstance(chunk, bytes):
187
+ audio_chunks.append(chunk)
188
+ audio_data = b"".join(audio_chunks)
189
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
190
+ response_chunks = []
191
+ async for chunk in stream:
192
+ if isinstance(chunk, str):
193
+ response_chunks.append(chunk)
194
+ response = "".join(response_chunks)
195
+ logger.info(f"Chat response: {response}")
196
+
197
+ if user and conversation:
198
+ assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
199
+ db.add(assistant_msg)
200
+ db.commit()
201
+ conversation.updated_at = datetime.utcnow()
202
+ db.commit()
203
+ return {
204
+ "response": response,
205
+ "conversation_id": conversation.conversation_id,
206
+ "conversation_url": f"https://mgzon-mgzon-app.hf.space/chat/{conversation.conversation_id}",
207
+ "conversation_title": conversation.title
208
+ }
209
+
210
  return {"response": response}
211
 
212
  @router.post("/api/audio-transcription")
 
216
  user: User = Depends(current_active_user),
217
  db: Session = Depends(get_db)
218
  ):
219
+ logger.info(f"Received audio transcription request for file: {file.filename}")
220
+
 
 
 
 
221
  if not user:
222
+ await handle_session(request)
223
+
224
+ conversation = None
225
+ if user:
226
+ title = "Audio Transcription"
227
+ conversation = db.query(Conversation).filter(Conversation.user_id == user.id).order_by(Conversation.updated_at.desc()).first()
228
+ if not conversation:
229
+ conversation_id = str(uuid.uuid4())
230
+ conversation = Conversation(
231
+ conversation_id=conversation_id,
232
+ user_id=user.id,
233
+ title=title
 
234
  )
235
+ db.add(conversation)
236
+ db.commit()
237
+ db.refresh(conversation)
238
+
239
+ user_msg = Message(role="user", content="Audio message", conversation_id=conversation.id)
240
+ db.add(user_msg)
241
+ db.commit()
242
+
243
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
244
  audio_data = await file.read()
245
  stream = request_generation(
 
249
  system_prompt="Transcribe the provided audio using Whisper.",
250
  model_name=model_name,
251
  temperature=0.7,
252
+ max_new_tokens=2048,
253
  input_type="audio",
254
  audio_data=audio_data,
255
  output_format="text"
256
  )
257
+ response_chunks = []
258
+ async for chunk in stream:
259
+ if isinstance(chunk, str):
260
+ response_chunks.append(chunk)
261
+ response = "".join(response_chunks)
262
+ logger.info(f"Audio transcription response: {response}")
263
+
264
+ if user and conversation:
265
+ assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
266
+ db.add(assistant_msg)
267
+ db.commit()
268
+ conversation.updated_at = datetime.utcnow()
269
+ db.commit()
270
+ return {
271
+ "transcription": response,
272
+ "conversation_id": conversation.conversation_id,
273
+ "conversation_url": f"https://mgzon-mgzon-app.hf.space/chat/{conversation.conversation_id}",
274
+ "conversation_title": conversation.title
275
+ }
276
+
277
  return {"transcription": response}
278
 
279
  @router.post("/api/text-to-speech")
 
283
  user: User = Depends(current_active_user),
284
  db: Session = Depends(get_db)
285
  ):
 
 
 
 
 
 
286
  if not user:
287
+ await handle_session(request)
288
+
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  text = req.get("text", "")
290
  model_name, api_endpoint = select_model("text to speech", input_type="tts")
291
  stream = request_generation(
 
295
  system_prompt="Convert the provided text to speech using a text-to-speech model.",
296
  model_name=model_name,
297
  temperature=0.7,
298
+ max_new_tokens=2048,
299
  input_type="tts",
300
  output_format="audio"
301
  )
302
+ audio_chunks = []
303
+ async for chunk in stream:
304
+ if isinstance(chunk, bytes):
305
+ audio_chunks.append(chunk)
306
+ audio_data = b"".join(audio_chunks)
307
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
308
 
309
  @router.post("/api/code")
 
313
  user: User = Depends(current_active_user),
314
  db: Session = Depends(get_db)
315
  ):
 
 
 
 
 
 
316
  if not user:
317
+ await handle_session(request)
318
+
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  framework = req.get("framework")
320
  task = req.get("task")
321
  code = req.get("code", "")
322
  output_format = req.get("output_format", "text")
323
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
324
+ preferred_model = user.preferred_model if user else None
325
+ model_name, api_endpoint = select_model(prompt, input_type="text", preferred_model=preferred_model)
326
+ system_prompt = "You are a coding expert. Provide detailed, well-commented code with examples and explanations."
327
+ if user and user.additional_info:
328
+ system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
329
+
330
  stream = request_generation(
331
  api_key=HF_TOKEN,
332
  api_base=api_endpoint,
333
  message=prompt,
334
+ system_prompt=system_prompt,
335
  model_name=model_name,
336
  temperature=0.7,
337
+ max_new_tokens=2048,
338
  input_type="text",
339
  output_format=output_format
340
  )
341
  if output_format == "audio":
342
+ audio_chunks = []
343
+ async for chunk in stream:
344
+ if isinstance(chunk, bytes):
345
+ audio_chunks.append(chunk)
346
+ audio_data = b"".join(audio_chunks)
347
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
348
+ response_chunks = []
349
+ async for chunk in stream:
350
+ if isinstance(chunk, str):
351
+ response_chunks.append(chunk)
352
+ response = "".join(response_chunks)
353
  return {"generated_code": response}
354
 
355
  @router.post("/api/analysis")
 
359
  user: User = Depends(current_active_user),
360
  db: Session = Depends(get_db)
361
  ):
 
 
 
 
 
 
362
  if not user:
363
+ await handle_session(request)
364
+
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  message = req.get("text", "")
366
  output_format = req.get("output_format", "text")
367
+ preferred_model = user.preferred_model if user else None
368
+ model_name, api_endpoint = select_model(message, input_type="text", preferred_model=preferred_model)
369
+ system_prompt = "You are an expert analyst. Provide detailed analysis with step-by-step reasoning and examples."
370
+ if user and user.additional_info:
371
+ system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
372
+
373
  stream = request_generation(
374
  api_key=HF_TOKEN,
375
  api_base=api_endpoint,
376
  message=message,
377
+ system_prompt=system_prompt,
378
  model_name=model_name,
379
  temperature=0.7,
380
+ max_new_tokens=2048,
381
  input_type="text",
382
  output_format=output_format
383
  )
384
  if output_format == "audio":
385
+ audio_chunks = []
386
+ async for chunk in stream:
387
+ if isinstance(chunk, bytes):
388
+ audio_chunks.append(chunk)
389
+ audio_data = b"".join(audio_chunks)
390
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
391
+ response_chunks = []
392
+ async for chunk in stream:
393
+ if isinstance(chunk, str):
394
+ response_chunks.append(chunk)
395
+ response = "".join(response_chunks)
396
  return {"analysis": response}
397
 
398
  @router.post("/api/image-analysis")
 
403
  user: User = Depends(current_active_user),
404
  db: Session = Depends(get_db)
405
  ):
 
 
 
 
 
 
406
  if not user:
407
+ await handle_session(request)
408
+
409
+ conversation = None
410
+ if user:
411
+ title = "Image Analysis"
412
+ conversation = db.query(Conversation).filter(Conversation.user_id == user.id).order_by(Conversation.updated_at.desc()).first()
413
+ if not conversation:
414
+ conversation_id = str(uuid.uuid4())
415
+ conversation = Conversation(
416
+ conversation_id=conversation_id,
417
+ user_id=user.id,
418
+ title=title
 
419
  )
420
+ db.add(conversation)
421
+ db.commit()
422
+ db.refresh(conversation)
423
+
424
+ user_msg = Message(role="user", content="Image analysis request", conversation_id=conversation.id)
425
+ db.add(user_msg)
426
+ db.commit()
427
+
428
+ preferred_model = user.preferred_model if user else None
429
+ model_name, api_endpoint = select_model("analyze image", input_type="image", preferred_model=preferred_model)
430
  image_data = await file.read()
431
+ system_prompt = "You are an expert in image analysis. Provide detailed descriptions or classifications based on the query."
432
+ if user and user.additional_info:
433
+ system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
434
+
435
  stream = request_generation(
436
  api_key=HF_TOKEN,
437
  api_base=api_endpoint,
438
  message="Analyze this image",
439
+ system_prompt=system_prompt,
440
  model_name=model_name,
441
  temperature=0.7,
442
+ max_new_tokens=2048,
443
  input_type="image",
444
  image_data=image_data,
445
  output_format=output_format
446
  )
447
  if output_format == "audio":
448
+ audio_chunks = []
449
+ async for chunk in stream:
450
+ if isinstance(chunk, bytes):
451
+ audio_chunks.append(chunk)
452
+ audio_data = b"".join(audio_chunks)
453
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
454
+ response_chunks = []
455
+ async for chunk in stream:
456
+ if isinstance(chunk, str):
457
+ response_chunks.append(chunk)
458
+ response = "".join(response_chunks)
459
+
460
+ if user and conversation:
461
+ assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
462
+ db.add(assistant_msg)
463
+ db.commit()
464
+ conversation.updated_at = datetime.utcnow()
465
+ db.commit()
466
+ return {
467
+ "image_analysis": response,
468
+ "conversation_id": conversation.conversation_id,
469
+ "conversation_url": f"https://mgzon-mgzon-app.hf.space/chat/{conversation.conversation_id}",
470
+ "conversation_title": conversation.title
471
+ }
472
+
473
  return {"image_analysis": response}
474
 
475
  @router.get("/api/test-model")
 
484
  return {"status": "success", "response": response.choices[0].message.content}
485
  except Exception as e:
486
  return {"status": "error", "message": str(e)}
487
+
488
+ @router.post("/api/conversations", response_model=ConversationOut)
489
+ async def create_conversation(
490
+ req: ConversationCreate,
491
+ user: User = Depends(current_active_user),
492
+ db: Session = Depends(get_db)
493
+ ):
494
+ if not user:
495
+ raise HTTPException(status_code=401, detail="Login required")
496
+ conversation_id = str(uuid.uuid4())
497
+ conversation = Conversation(
498
+ conversation_id=conversation_id,
499
+ title=req.title or "Untitled Conversation",
500
+ user_id=user.id
501
+ )
502
+ db.add(conversation)
503
+ db.commit()
504
+ db.refresh(conversation)
505
+ return ConversationOut.from_orm(conversation)
506
+
507
+ @router.get("/api/conversations/{conversation_id}", response_model=ConversationOut)
508
+ async def get_conversation(
509
+ conversation_id: str,
510
+ user: User = Depends(current_active_user),
511
+ db: Session = Depends(get_db)
512
+ ):
513
+ if not user:
514
+ raise HTTPException(status_code=401, detail="Login required")
515
+ conversation = db.query(Conversation).filter(
516
+ Conversation.conversation_id == conversation_id,
517
+ Conversation.user_id == user.id
518
+ ).first()
519
+ if not conversation:
520
+ raise HTTPException(status_code=404, detail="Conversation not found")
521
+ db.add(conversation)
522
+ db.commit()
523
+ return ConversationOut.from_orm(conversation)
524
+
525
+ @router.get("/api/conversations", response_model=List[ConversationOut])
526
+ async def list_conversations(
527
+ user: User = Depends(current_active_user),
528
+ db: Session = Depends(get_db)
529
+ ):
530
+ if not user:
531
+ raise HTTPException(status_code=401, detail="Login required")
532
+ conversations = db.query(Conversation).filter(Conversation.user_id == user.id).order_by(Conversation.created_at.desc()).all()
533
+ return conversations
534
+
535
+ @router.put("/api/conversations/{conversation_id}/title")
536
+ async def update_conversation_title(
537
+ conversation_id: str,
538
+ title: str,
539
+ user: User = Depends(current_active_user),
540
+ db: Session = Depends(get_db)
541
+ ):
542
+ if not user:
543
+ raise HTTPException(status_code=401, detail="Login required")
544
+ conversation = db.query(Conversation).filter(
545
+ Conversation.conversation_id == conversation_id,
546
+ Conversation.user_id == user.id
547
+ ).first()
548
+ if not conversation:
549
+ raise HTTPException(status_code=404, detail="Conversation not found")
550
+
551
+ conversation.title = title
552
+ conversation.updated_at = datetime.utcnow()
553
+ db.commit()
554
+ return {"message": "Conversation title updated", "title": conversation.title}
555
+
556
+ @router.delete("/api/conversations/{conversation_id}")
557
+ async def delete_conversation(
558
+ conversation_id: str,
559
+ user: User = Depends(current_active_user),
560
+ db: Session = Depends(get_db)
561
+ ):
562
+ if not user:
563
+ raise HTTPException(status_code=401, detail="Login required")
564
+ conversation = db.query(Conversation).filter(
565
+ Conversation.conversation_id == conversation_id,
566
+ Conversation.user_id == user.id
567
+ ).first()
568
+ if not conversation:
569
+ raise HTTPException(status_code=404, detail="Conversation not found")
570
+
571
+ db.query(Message).filter(Message.conversation_id == conversation.id).delete()
572
+ db.delete(conversation)
573
+ db.commit()
574
+ return {"message": "Conversation deleted successfully"}
575
+
576
+ @router.get("/users/me")
577
+ async def get_user_settings(user: User = Depends(current_active_user)):
578
+ if not user:
579
+ raise HTTPException(status_code=401, detail="Login required")
580
+ return {
581
+ "id": user.id,
582
+ "email": user.email,
583
+ "display_name": user.display_name,
584
+ "preferred_model": user.preferred_model,
585
+ "job_title": user.job_title,
586
+ "education": user.education,
587
+ "interests": user.interests,
588
+ "additional_info": user.additional_info,
589
+ "conversation_style": user.conversation_style,
590
+ "is_active": user.is_active,
591
+ "is_superuser": user.is_superuser
592
+ }
593
+
594
+ @router.put("/users/me")
595
+ async def update_user_settings(
596
+ settings: UserUpdate,
597
+ user: User = Depends(current_active_user),
598
+ db: Session = Depends(get_db)
599
+ ):
600
+ if not user:
601
+ raise HTTPException(status_code=401, detail="Login required")
602
+
603
+ # Validate preferred_model
604
+ if settings.preferred_model and settings.preferred_model not in MODEL_ALIASES:
605
+ raise HTTPException(status_code=400, detail="Invalid model alias")
606
+
607
+ # Update user settings
608
+ if settings.display_name is not None:
609
+ user.display_name = settings.display_name
610
+ if settings.preferred_model is not None:
611
+ user.preferred_model = settings.preferred_model
612
+ if settings.job_title is not None:
613
+ user.job_title = settings.job_title
614
+ if settings.education is not None:
615
+ user.education = settings.education
616
+ if settings.interests is not None:
617
+ user.interests = settings.interests
618
+ if settings.additional_info is not None:
619
+ user.additional_info = settings.additional_info
620
+ if settings.conversation_style is not None:
621
+ user.conversation_style = settings.conversation_style
622
+
623
+ db.commit()
624
+ db.refresh(user)
625
+ return {"message": "Settings updated successfully", "user": {
626
+ "id": user.id,
627
+ "email": user.email,
628
+ "display_name": user.display_name,
629
+ "preferred_model": user.preferred_model,
630
+ "job_title": user.job_title,
631
+ "education": user.education,
632
+ "interests": user.interests,
633
+ "additional_info": user.additional_info,
634
+ "conversation_style": user.conversation_style,
635
+ "is_active": user.is_active,
636
+ "is_superuser": user.is_superuser
637
+ }}
api/models.py CHANGED
@@ -1,10 +1,12 @@
1
  from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyBaseOAuthAccountTable
2
- from sqlalchemy import Column, Integer, String, Boolean
3
  from sqlalchemy.orm import relationship
4
  from sqlalchemy.ext.declarative import declarative_base
5
  from pydantic import BaseModel, Field
6
  from typing import List, Optional
7
  from fastapi_users import schemas
 
 
8
 
9
  Base = declarative_base()
10
 
@@ -19,10 +21,42 @@ class User(SQLAlchemyBaseUserTable, Base):
19
  __tablename__ = "users"
20
  id = Column(Integer, primary_key=True, index=True)
21
  email = Column(String, unique=True, index=True, nullable=False)
22
- hashed_password = Column(String, nullable=True) # nullable لأن OAuth ممكن ما يحتاج باسورد
23
  is_active = Column(Boolean, default=True)
24
  is_superuser = Column(Boolean, default=False)
25
- oauth_accounts = relationship("OAuthAccount", back_populates="user")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # Pydantic schemas for fastapi-users
28
  class UserRead(schemas.BaseUser[int]):
@@ -30,12 +64,54 @@ class UserRead(schemas.BaseUser[int]):
30
  email: str
31
  is_active: bool = True
32
  is_superuser: bool = False
 
 
 
 
 
 
 
33
 
34
  class UserCreate(schemas.BaseUserCreate):
35
  email: str
36
  password: str
37
  is_active: Optional[bool] = True
38
  is_superuser: Optional[bool] = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # نموذج طلب الاستعلام
41
  class QueryRequest(BaseModel):
@@ -43,6 +119,10 @@ class QueryRequest(BaseModel):
43
  system_prompt: Optional[str] = "You are an expert assistant providing detailed, comprehensive, and well-structured responses."
44
  history: Optional[List[dict]] = []
45
  temperature: Optional[float] = 0.7
46
- max_new_tokens: Optional[int] = 128000
47
  enable_browsing: Optional[bool] = True
48
  output_format: Optional[str] = "text"
 
 
 
 
 
1
  from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyBaseOAuthAccountTable
2
+ from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, DateTime
3
  from sqlalchemy.orm import relationship
4
  from sqlalchemy.ext.declarative import declarative_base
5
  from pydantic import BaseModel, Field
6
  from typing import List, Optional
7
  from fastapi_users import schemas
8
+ from datetime import datetime
9
+ import uuid
10
 
11
  Base = declarative_base()
12
 
 
21
  __tablename__ = "users"
22
  id = Column(Integer, primary_key=True, index=True)
23
  email = Column(String, unique=True, index=True, nullable=False)
24
+ hashed_password = Column(String, nullable=True)
25
  is_active = Column(Boolean, default=True)
26
  is_superuser = Column(Boolean, default=False)
27
+ display_name = Column(String, nullable=True) # الاسم المستعار للمستخدم
28
+ preferred_model = Column(String, nullable=True) # النموذج المفضل (اسم وهمي)
29
+ job_title = Column(String, nullable=True) # الوظيفة
30
+ education = Column(String, nullable=True) # التعليم
31
+ interests = Column(Text, nullable=True) # الاهتمامات
32
+ additional_info = Column(Text, nullable=True) # معلومات إضافية
33
+ conversation_style = Column(String, nullable=True) # نمط المحادثة (مثل: موجز، تحليلي)
34
+ oauth_accounts = relationship("OAuthAccount", back_populates="user", cascade="all, delete-orphan")
35
+ conversations = relationship("Conversation", back_populates="user", cascade="all, delete-orphan")
36
+
37
+ # نموذج المحادثة
38
+ class Conversation(Base):
39
+ __tablename__ = "conversations"
40
+ id = Column(Integer, primary_key=True, index=True)
41
+ conversation_id = Column(String, unique=True, index=True, default=lambda: str(uuid.uuid4()))
42
+ title = Column(String, nullable=True)
43
+ user_id = Column(Integer, ForeignKey("users.id"))
44
+ created_at = Column(DateTime, default=datetime.utcnow)
45
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
46
+
47
+ user = relationship("User", back_populates="conversations")
48
+ messages = relationship("Message", back_populates="conversation", cascade="all, delete-orphan")
49
+
50
+ # نموذج الرسالة
51
+ class Message(Base):
52
+ __tablename__ = "messages"
53
+ id = Column(Integer, primary_key=True, index=True)
54
+ role = Column(String)
55
+ content = Column(Text)
56
+ conversation_id = Column(Integer, ForeignKey("conversations.id"))
57
+ created_at = Column(DateTime, default=datetime.utcnow)
58
+
59
+ conversation = relationship("Conversation", back_populates="messages")
60
 
61
  # Pydantic schemas for fastapi-users
62
  class UserRead(schemas.BaseUser[int]):
 
64
  email: str
65
  is_active: bool = True
66
  is_superuser: bool = False
67
+ display_name: Optional[str] = None
68
+ preferred_model: Optional[str] = None
69
+ job_title: Optional[str] = None
70
+ education: Optional[str] = None
71
+ interests: Optional[str] = None
72
+ additional_info: Optional[str] = None
73
+ conversation_style: Optional[str] = None
74
 
75
  class UserCreate(schemas.BaseUserCreate):
76
  email: str
77
  password: str
78
  is_active: Optional[bool] = True
79
  is_superuser: Optional[bool] = False
80
+ display_name: Optional[str] = None
81
+ preferred_model: Optional[str] = None
82
+ job_title: Optional[str] = None
83
+ education: Optional[str] = None
84
+ interests: Optional[str] = None
85
+ additional_info: Optional[str] = None
86
+ conversation_style: Optional[str] = None
87
+
88
+ # Pydantic schema for updating user settings
89
+ class UserUpdate(BaseModel):
90
+ display_name: Optional[str] = None
91
+ preferred_model: Optional[str] = None
92
+ job_title: Optional[str] = None
93
+ education: Optional[str] = None
94
+ interests: Optional[str] = None
95
+ additional_info: Optional[str] = None
96
+ conversation_style: Optional[str] = None
97
+
98
+ # Pydantic schemas للمحادثات
99
+ class MessageOut(BaseModel):
100
+ id: int
101
+ role: str
102
+ content: str
103
+ created_at: datetime
104
+
105
+ class ConversationCreate(BaseModel):
106
+ title: Optional[str] = None
107
+
108
+ class ConversationOut(BaseModel):
109
+ id: int
110
+ conversation_id: str
111
+ title: Optional[str]
112
+ created_at: datetime
113
+ updated_at: datetime
114
+ messages: List[MessageOut] = []
115
 
116
  # نموذج طلب الاستعلام
117
  class QueryRequest(BaseModel):
 
119
  system_prompt: Optional[str] = "You are an expert assistant providing detailed, comprehensive, and well-structured responses."
120
  history: Optional[List[dict]] = []
121
  temperature: Optional[float] = 0.7
122
+ max_new_tokens: Optional[int] = 2048
123
  enable_browsing: Optional[bool] = True
124
  output_format: Optional[str] = "text"
125
+ title: Optional[str] = None
126
+
127
+ ConversationOut.model_revalidate = True
128
+ MessageOut.model_revalidate = True
main.py CHANGED
@@ -1,7 +1,10 @@
 
 
 
1
  import os
2
  import logging
3
  from fastapi import FastAPI, Request, Depends, HTTPException, status, Query
4
- from fastapi.responses import HTMLResponse, RedirectResponse, PlainTextResponse
5
  from fastapi.staticfiles import StaticFiles
6
  from fastapi.templating import Jinja2Templates
7
  from starlette.middleware.base import BaseHTTPMiddleware
@@ -11,40 +14,53 @@ from fastapi.middleware.cors import CORSMiddleware
11
  from api.endpoints import router as api_router
12
  from api.auth import fastapi_users, auth_backend, current_active_user, google_oauth_client, github_oauth_client
13
  from api.database import get_db, engine, Base
14
- from api.models import User, UserRead, UserCreate
15
  from motor.motor_asyncio import AsyncIOMotorClient
16
  from pydantic import BaseModel
17
  from typing import List
18
  import uvicorn
19
  import markdown2
 
 
 
 
 
20
 
21
- # إعداد التسجيل
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger(__name__)
 
24
 
25
- # تحقق من الملفات في /app/
26
- logger.info("Files in /app/: %s", os.listdir("/app"))
27
-
28
- # إعداد العميل لـ Hugging Face Inference API
29
  HF_TOKEN = os.getenv("HF_TOKEN")
30
  if not HF_TOKEN:
31
  logger.error("HF_TOKEN is not set in environment variables.")
32
  raise ValueError("HF_TOKEN is required for Inference API.")
33
 
34
- # إعداد MongoDB
35
  MONGO_URI = os.getenv("MONGODB_URI")
36
  if not MONGO_URI:
37
  logger.error("MONGODB_URI is not set in environment variables.")
38
  raise ValueError("MONGODB_URI is required for MongoDB.")
39
 
 
 
 
 
 
 
40
  client = AsyncIOMotorClient(MONGO_URI)
41
- db = client["hager"]
 
42
 
43
- # إعداد Jinja2 مع دعم Markdown
 
 
 
 
44
  templates = Jinja2Templates(directory="templates")
45
  templates.env.filters['markdown'] = lambda text: markdown2.markdown(text)
46
 
47
- # موديل للمقالات
48
  class BlogPost(BaseModel):
49
  id: str
50
  title: str
@@ -53,32 +69,40 @@ class BlogPost(BaseModel):
53
  date: str
54
  created_at: str
55
 
56
- # إعدادات الـ queue
57
  QUEUE_SIZE = int(os.getenv("QUEUE_SIZE", 80))
58
  CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
59
 
60
- # إعداد FastAPI
61
  app = FastAPI(title="MGZon Chatbot API")
62
 
63
- # إضافة SessionMiddleware
64
- app.add_middleware(SessionMiddleware, secret_key=os.getenv("JWT_SECRET"))
65
 
66
- # إنشاء الجداول تلقائيًا
67
  Base.metadata.create_all(bind=engine)
68
 
69
- # ربط الملفات الثابتة
 
70
  app.mount("/static", StaticFiles(directory="static"), name="static")
71
 
72
- # إضافة CORS middleware
73
  app.add_middleware(
74
  CORSMiddleware,
75
- allow_origins=["https://mgzon-mgzon-app.hf.space"],
 
 
 
 
 
76
  allow_credentials=True,
77
  allow_methods=["*"],
78
  allow_headers=["*"],
 
 
79
  )
80
 
81
- # إضافة auth routers
82
  app.include_router(
83
  fastapi_users.get_auth_router(auth_backend),
84
  prefix="/auth/jwt",
@@ -95,17 +119,18 @@ app.include_router(
95
  tags=["users"],
96
  )
97
  app.include_router(
98
- fastapi_users.get_oauth_router(google_oauth_client, auth_backend, os.getenv("JWT_SECRET"), redirect_url="https://mgzon-mgzon-app.hf.space/auth/google/callback"),
99
  prefix="/auth/google",
100
  tags=["auth"],
101
  )
102
  app.include_router(
103
- fastapi_users.get_oauth_router(github_oauth_client, auth_backend, os.getenv("JWT_SECRET"), redirect_url="https://mgzon-mgzon-app.hf.space/auth/github/callback"),
104
  prefix="/auth/github",
105
  tags=["auth"],
106
  )
 
107
 
108
- # Middleware لمعالجة 404
109
  class NotFoundMiddleware(BaseHTTPMiddleware):
110
  async def dispatch(self, request: Request, call_next):
111
  try:
@@ -120,107 +145,163 @@ class NotFoundMiddleware(BaseHTTPMiddleware):
120
 
121
  app.add_middleware(NotFoundMiddleware)
122
 
123
- # Root endpoint
124
  @app.get("/", response_class=HTMLResponse)
125
- async def root(request: Request, user: User = Depends(fastapi_users.current_user(optional=True))):
126
  return templates.TemplateResponse("index.html", {"request": request, "user": user})
127
 
 
 
 
 
 
128
  # Login page
129
  @app.get("/login", response_class=HTMLResponse)
130
- async def login_page(request: Request, user: User = Depends(fastapi_users.current_user(optional=True))):
131
  if user:
132
  return RedirectResponse(url="/chat", status_code=302)
133
  return templates.TemplateResponse("login.html", {"request": request})
134
 
135
  # Register page
136
  @app.get("/register", response_class=HTMLResponse)
137
- async def register_page(request: Request, user: User = Depends(fastapi_users.current_user(optional=True))):
138
  if user:
139
  return RedirectResponse(url="/chat", status_code=302)
140
  return templates.TemplateResponse("register.html", {"request": request})
141
 
142
- # Chat endpoint
143
  @app.get("/chat", response_class=HTMLResponse)
144
- async def chat(request: Request, user: User = Depends(fastapi_users.current_user(optional=True))):
145
  return templates.TemplateResponse("chat.html", {"request": request, "user": user})
146
 
147
- # About endpoint
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  @app.get("/about", response_class=HTMLResponse)
149
- async def about(request: Request, user: User = Depends(fastapi_users.current_user(optional=True))):
150
  return templates.TemplateResponse("about.html", {"request": request, "user": user})
151
 
152
- # Blog endpoint (قائمة المقالات)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  @app.get("/blog", response_class=HTMLResponse)
154
  async def blog(request: Request, skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=100)):
155
- posts = await db.blog_posts.find().skip(skip).limit(limit).to_list(limit)
156
  return templates.TemplateResponse("blog.html", {"request": request, "posts": posts})
157
 
158
- # Blog post endpoint (عرض مقالة كاملة)
159
  @app.get("/blog/{post_id}", response_class=HTMLResponse)
160
  async def blog_post(request: Request, post_id: str):
161
- post = await db.blog_posts.find_one({"id": post_id})
162
  if not post:
163
  raise HTTPException(status_code=404, detail="Post not found")
164
  return templates.TemplateResponse("blog_post.html", {"request": request, "post": post})
165
 
166
- # Docs endpoint
167
  @app.get("/docs", response_class=HTMLResponse)
168
  async def docs(request: Request):
169
  return templates.TemplateResponse("docs.html", {"request": request})
170
 
171
- # Swagger UI endpoint
172
  @app.get("/swagger", response_class=HTMLResponse)
173
  async def swagger_ui():
174
  return get_swagger_ui_html(openapi_url="/openapi.json", title="MGZon API Documentation")
175
 
176
- # Sitemap endpoint (ديناميكي)
177
  @app.get("/sitemap.xml", response_class=PlainTextResponse)
178
  async def sitemap():
179
- posts = await db.blog_posts.find().to_list(100)
 
180
  xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
181
  xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
 
182
  xml += ' <url>\n'
183
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/</loc>\n'
184
- xml += ' <lastmod>2025-09-01</lastmod>\n'
185
  xml += ' <changefreq>daily</changefreq>\n'
186
  xml += ' <priority>1.0</priority>\n'
187
  xml += ' </url>\n'
188
  xml += ' <url>\n'
189
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/chat</loc>\n'
190
- xml += ' <lastmod>2025-09-01</lastmod>\n'
191
  xml += ' <changefreq>daily</changefreq>\n'
192
  xml += ' <priority>0.8</priority>\n'
193
  xml += ' </url>\n'
194
  xml += ' <url>\n'
195
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/about</loc>\n'
196
- xml += ' <lastmod>2025-09-01</lastmod>\n'
197
  xml += ' <changefreq>weekly</changefreq>\n'
198
  xml += ' <priority>0.7</priority>\n'
199
  xml += ' </url>\n'
200
  xml += ' <url>\n'
201
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/login</loc>\n'
202
- xml += ' <lastmod>2025-09-01</lastmod>\n'
203
  xml += ' <changefreq>weekly</changefreq>\n'
204
  xml += ' <priority>0.8</priority>\n'
205
  xml += ' </url>\n'
206
  xml += ' <url>\n'
207
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/register</loc>\n'
208
- xml += ' <lastmod>2025-09-01</lastmod>\n'
209
  xml += ' <changefreq>weekly</changefreq>\n'
210
  xml += ' <priority>0.8</priority>\n'
211
  xml += ' </url>\n'
212
  xml += ' <url>\n'
213
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/docs</loc>\n'
214
- xml += ' <lastmod>2025-09-01</lastmod>\n'
215
  xml += ' <changefreq>weekly</changefreq>\n'
216
  xml += ' <priority>0.9</priority>\n'
217
  xml += ' </url>\n'
218
  xml += ' <url>\n'
219
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/blog</loc>\n'
220
- xml += ' <lastmod>2025-09-01</lastmod>\n'
221
  xml += ' <changefreq>daily</changefreq>\n'
222
  xml += ' <priority>0.9</priority>\n'
223
  xml += ' </url>\n'
 
224
  for post in posts:
225
  xml += ' <url>\n'
226
  xml += f' <loc>https://mgzon-mgzon-app.hf.space/blog/{post["id"]}</loc>\n'
@@ -231,14 +312,16 @@ async def sitemap():
231
  xml += '</urlset>'
232
  return xml
233
 
234
- # Redirect لـ /gradio
235
  @app.get("/gradio", response_class=RedirectResponse)
236
  async def launch_chatbot():
237
  return RedirectResponse(url="/chat", status_code=302)
238
 
239
- # ربط API endpoints
240
- app.include_router(api_router)
 
 
241
 
242
- # تشغيل الخادم
243
  if __name__ == "__main__":
244
  uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
 
1
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
  import os
5
  import logging
6
  from fastapi import FastAPI, Request, Depends, HTTPException, status, Query
7
+ from fastapi.responses import HTMLResponse, RedirectResponse, PlainTextResponse, FileResponse
8
  from fastapi.staticfiles import StaticFiles
9
  from fastapi.templating import Jinja2Templates
10
  from starlette.middleware.base import BaseHTTPMiddleware
 
14
  from api.endpoints import router as api_router
15
  from api.auth import fastapi_users, auth_backend, current_active_user, google_oauth_client, github_oauth_client
16
  from api.database import get_db, engine, Base
17
+ from api.models import User, UserRead, UserCreate, Conversation
18
  from motor.motor_asyncio import AsyncIOMotorClient
19
  from pydantic import BaseModel
20
  from typing import List
21
  import uvicorn
22
  import markdown2
23
+ from sqlalchemy.orm import Session
24
+ from pathlib import Path
25
+ from hashlib import md5
26
+ from datetime import datetime
27
+ import re
28
 
29
+ # Setup logging for debugging and monitoring
30
  logging.basicConfig(level=logging.INFO)
31
  logger = logging.getLogger(__name__)
32
+ logger.info("Files in current dir: %s", os.listdir(os.getcwd()))
33
 
34
+ # Check environment variables for required configurations
 
 
 
35
  HF_TOKEN = os.getenv("HF_TOKEN")
36
  if not HF_TOKEN:
37
  logger.error("HF_TOKEN is not set in environment variables.")
38
  raise ValueError("HF_TOKEN is required for Inference API.")
39
 
 
40
  MONGO_URI = os.getenv("MONGODB_URI")
41
  if not MONGO_URI:
42
  logger.error("MONGODB_URI is not set in environment variables.")
43
  raise ValueError("MONGODB_URI is required for MongoDB.")
44
 
45
+ JWT_SECRET = os.getenv("JWT_SECRET")
46
+ if not JWT_SECRET or len(JWT_SECRET) < 32:
47
+ logger.error("JWT_SECRET is not set or too short.")
48
+ raise ValueError("JWT_SECRET is required (at least 32 characters).")
49
+
50
+ # MongoDB setup for blog posts and session message counts
51
  client = AsyncIOMotorClient(MONGO_URI)
52
+ mongo_db = client["hager"]
53
+ session_message_counts = mongo_db["session_message_counts"]
54
 
55
+ # Create MongoDB index for session_id to ensure uniqueness
56
+ async def setup_mongo_index():
57
+ await session_message_counts.create_index("session_id", unique=True)
58
+
59
+ # Jinja2 setup with Markdown filter for rendering Markdown content
60
  templates = Jinja2Templates(directory="templates")
61
  templates.env.filters['markdown'] = lambda text: markdown2.markdown(text)
62
 
63
+ # Pydantic model for blog posts
64
  class BlogPost(BaseModel):
65
  id: str
66
  title: str
 
69
  date: str
70
  created_at: str
71
 
72
+ # Application settings from environment variables
73
  QUEUE_SIZE = int(os.getenv("QUEUE_SIZE", 80))
74
  CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
75
 
76
+ # Initialize FastAPI app
77
  app = FastAPI(title="MGZon Chatbot API")
78
 
79
+ # Add SessionMiddleware for handling non-logged-in user sessions
80
+ app.add_middleware(SessionMiddleware, secret_key=JWT_SECRET)
81
 
82
+ # Create SQLAlchemy database tables
83
  Base.metadata.create_all(bind=engine)
84
 
85
+ # Mount static files directory
86
+ os.makedirs("static", exist_ok=True)
87
  app.mount("/static", StaticFiles(directory="static"), name="static")
88
 
89
+ # CORS setup to allow requests from specific origins
90
  app.add_middleware(
91
  CORSMiddleware,
92
+ allow_origins=[
93
+ "https://mgzon-mgzon-app.hf.space", # Production domain
94
+ "http://localhost:7860", # Local development
95
+ "https://mgzon-mgzon-app.hf.space/users/me", # For user settings endpoint
96
+ # Add staging domain here if needed, e.g., "https://staging.mgzon-mgzon-app.hf.space"
97
+ ],
98
  allow_credentials=True,
99
  allow_methods=["*"],
100
  allow_headers=["*"],
101
+ # Optional: Uncomment to support subdomains dynamically
102
+ # allow_origin_regex=r"https?://.*\.mgzon-mgzon-app\.hf\.space|http://localhost:7860",
103
  )
104
 
105
+ # Include routers for authentication, user management, and API endpoints
106
  app.include_router(
107
  fastapi_users.get_auth_router(auth_backend),
108
  prefix="/auth/jwt",
 
119
  tags=["users"],
120
  )
121
  app.include_router(
122
+ fastapi_users.get_oauth_router(google_oauth_client, auth_backend, JWT_SECRET, redirect_url="https://mgzon-mgzon-app.hf.space/auth/google/callback"),
123
  prefix="/auth/google",
124
  tags=["auth"],
125
  )
126
  app.include_router(
127
+ fastapi_users.get_oauth_router(github_oauth_client, auth_backend, JWT_SECRET, redirect_url="https://mgzon-mgzon-app.hf.space/auth/github/callback"),
128
  prefix="/auth/github",
129
  tags=["auth"],
130
  )
131
+ app.include_router(api_router)
132
 
133
+ # Custom middleware for handling 404 and 500 errors
134
  class NotFoundMiddleware(BaseHTTPMiddleware):
135
  async def dispatch(self, request: Request, call_next):
136
  try:
 
145
 
146
  app.add_middleware(NotFoundMiddleware)
147
 
148
+ # Root endpoint for homepage
149
  @app.get("/", response_class=HTMLResponse)
150
+ async def root(request: Request, user: User = Depends(current_active_user)):
151
  return templates.TemplateResponse("index.html", {"request": request, "user": user})
152
 
153
+ # Google verification endpoint
154
+ @app.get("/google97468ef1f6b6e804.html", response_class=PlainTextResponse)
155
+ async def google_verification():
156
+ return "google-site-verification: google97468ef1f6b6e804.html"
157
+
158
  # Login page
159
  @app.get("/login", response_class=HTMLResponse)
160
+ async def login_page(request: Request, user: User = Depends(current_active_user)):
161
  if user:
162
  return RedirectResponse(url="/chat", status_code=302)
163
  return templates.TemplateResponse("login.html", {"request": request})
164
 
165
  # Register page
166
  @app.get("/register", response_class=HTMLResponse)
167
+ async def register_page(request: Request, user: User = Depends(current_active_user)):
168
  if user:
169
  return RedirectResponse(url="/chat", status_code=302)
170
  return templates.TemplateResponse("register.html", {"request": request})
171
 
172
+ # Chat page
173
  @app.get("/chat", response_class=HTMLResponse)
174
+ async def chat(request: Request, user: User = Depends(current_active_user)):
175
  return templates.TemplateResponse("chat.html", {"request": request, "user": user})
176
 
177
+ # Specific conversation page
178
+ @app.get("/chat/{conversation_id}", response_class=HTMLResponse)
179
+ async def chat_conversation(
180
+ request: Request,
181
+ conversation_id: str,
182
+ user: User = Depends(current_active_user),
183
+ db: Session = Depends(get_db)
184
+ ):
185
+ if not user:
186
+ return RedirectResponse(url="/login", status_code=302)
187
+ conversation = db.query(Conversation).filter(
188
+ Conversation.conversation_id == conversation_id,
189
+ Conversation.user_id == user.id
190
+ ).first()
191
+ if not conversation:
192
+ raise HTTPException(status_code=404, detail="Conversation not found")
193
+ return templates.TemplateResponse(
194
+ "chat.html",
195
+ {
196
+ "request": request,
197
+ "user": user,
198
+ "conversation_id": conversation.conversation_id,
199
+ "conversation_title": conversation.title or "Untitled Conversation"
200
+ }
201
+ )
202
+
203
+ # About page
204
  @app.get("/about", response_class=HTMLResponse)
205
+ async def about(request: Request, user: User = Depends(current_active_user)):
206
  return templates.TemplateResponse("about.html", {"request": request, "user": user})
207
 
208
+ # Serve static files with caching and ETag support
209
+ @app.get("/static/{path:path}")
210
+ async def serve_static(path: str):
211
+ # Remove query parameters (e.g., ?v=1.0) for versioning
212
+ clean_path = re.sub(r'\?.*', '', path)
213
+ file_path = Path("static") / clean_path
214
+ if not file_path.exists():
215
+ raise HTTPException(status_code=404, detail="File not found")
216
+ # Set cache duration: 1 year for images, 1 hour for JS/CSS
217
+ cache_duration = 31536000 # 1 year
218
+ if clean_path.endswith(('.js', '.css')):
219
+ cache_duration = 3600 # 1 hour
220
+ # Generate ETag and Last-Modified headers
221
+ with open(file_path, "rb") as f:
222
+ file_hash = md5(f.read()).hexdigest()
223
+ headers = {
224
+ "Cache-Control": f"public, max-age={cache_duration}",
225
+ "ETag": file_hash,
226
+ "Last-Modified": datetime.utcfromtimestamp(file_path.stat().st_mtime).strftime('%a, %d %b %Y %H:%M:%S GMT')
227
+ }
228
+ return FileResponse(file_path, headers=headers)
229
+
230
+ # Blog page with pagination
231
  @app.get("/blog", response_class=HTMLResponse)
232
  async def blog(request: Request, skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=100)):
233
+ posts = await mongo_db.blog_posts.find().skip(skip).limit(limit).to_list(limit)
234
  return templates.TemplateResponse("blog.html", {"request": request, "posts": posts})
235
 
236
+ # Individual blog post
237
  @app.get("/blog/{post_id}", response_class=HTMLResponse)
238
  async def blog_post(request: Request, post_id: str):
239
+ post = await mongo_db.blog_posts.find_one({"id": post_id})
240
  if not post:
241
  raise HTTPException(status_code=404, detail="Post not found")
242
  return templates.TemplateResponse("blog_post.html", {"request": request, "post": post})
243
 
244
+ # Docs page
245
  @app.get("/docs", response_class=HTMLResponse)
246
  async def docs(request: Request):
247
  return templates.TemplateResponse("docs.html", {"request": request})
248
 
249
+ # Swagger UI for API documentation
250
  @app.get("/swagger", response_class=HTMLResponse)
251
  async def swagger_ui():
252
  return get_swagger_ui_html(openapi_url="/openapi.json", title="MGZon API Documentation")
253
 
254
+ # Sitemap with dynamic dates
255
  @app.get("/sitemap.xml", response_class=PlainTextResponse)
256
  async def sitemap():
257
+ posts = await mongo_db.blog_posts.find().to_list(100)
258
+ current_date = datetime.utcnow().strftime('%Y-%m-%d')
259
  xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
260
  xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
261
+ # Main pages with dynamic lastmod
262
  xml += ' <url>\n'
263
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/</loc>\n'
264
+ xml += f' <lastmod>{current_date}</lastmod>\n'
265
  xml += ' <changefreq>daily</changefreq>\n'
266
  xml += ' <priority>1.0</priority>\n'
267
  xml += ' </url>\n'
268
  xml += ' <url>\n'
269
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/chat</loc>\n'
270
+ xml += f' <lastmod>{current_date}</lastmod>\n'
271
  xml += ' <changefreq>daily</changefreq>\n'
272
  xml += ' <priority>0.8</priority>\n'
273
  xml += ' </url>\n'
274
  xml += ' <url>\n'
275
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/about</loc>\n'
276
+ xml += f' <lastmod>{current_date}</lastmod>\n'
277
  xml += ' <changefreq>weekly</changefreq>\n'
278
  xml += ' <priority>0.7</priority>\n'
279
  xml += ' </url>\n'
280
  xml += ' <url>\n'
281
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/login</loc>\n'
282
+ xml += f' <lastmod>{current_date}</lastmod>\n'
283
  xml += ' <changefreq>weekly</changefreq>\n'
284
  xml += ' <priority>0.8</priority>\n'
285
  xml += ' </url>\n'
286
  xml += ' <url>\n'
287
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/register</loc>\n'
288
+ xml += f' <lastmod>{current_date}</lastmod>\n'
289
  xml += ' <changefreq>weekly</changefreq>\n'
290
  xml += ' <priority>0.8</priority>\n'
291
  xml += ' </url>\n'
292
  xml += ' <url>\n'
293
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/docs</loc>\n'
294
+ xml += f' <lastmod>{current_date}</lastmod>\n'
295
  xml += ' <changefreq>weekly</changefreq>\n'
296
  xml += ' <priority>0.9</priority>\n'
297
  xml += ' </url>\n'
298
  xml += ' <url>\n'
299
  xml += ' <loc>https://mgzon-mgzon-app.hf.space/blog</loc>\n'
300
+ xml += f' <lastmod>{current_date}</lastmod>\n'
301
  xml += ' <changefreq>daily</changefreq>\n'
302
  xml += ' <priority>0.9</priority>\n'
303
  xml += ' </url>\n'
304
+ # Blog posts from MongoDB
305
  for post in posts:
306
  xml += ' <url>\n'
307
  xml += f' <loc>https://mgzon-mgzon-app.hf.space/blog/{post["id"]}</loc>\n'
 
312
  xml += '</urlset>'
313
  return xml
314
 
315
+ # Redirect /gradio to /chat
316
  @app.get("/gradio", response_class=RedirectResponse)
317
  async def launch_chatbot():
318
  return RedirectResponse(url="/chat", status_code=302)
319
 
320
+ # Startup event to initialize MongoDB index
321
+ @app.on_event("startup")
322
+ async def startup_event():
323
+ await setup_mongo_index()
324
 
325
+ # Run the app
326
  if __name__ == "__main__":
327
  uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
static/css/sidebar.css ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* sidebar.css */
2
+
3
+ /* تنسيق السايدبار الأساسي */
4
+ #sidebar {
5
+ width: 256px; /* عرض ثابت للسايدبار */
6
+ background: linear-gradient(to bottom, rgba(31, 41, 55, 0.9), rgba(20, 78, 85, 0.9)); /* تدرج لوني */
7
+ backdrop-filter: blur(10px); /* تأثير البلور */
8
+ -webkit-backdrop-filter: blur(10px); /* دعم WebKit */
9
+ box-shadow: 2px 0 10px rgba(0, 0, 0, 0.3); /* ظل خفيف */
10
+ transition: transform 0.3s ease-in-out; /* حركة سلسة */
11
+ z-index: 50; /* التأكد إن السايدبار فوق العناصر الأخرى */
12
+ }
13
+
14
+ /* تنسيق روابط التنقل */
15
+ #sidebar nav ul li a {
16
+ display: flex;
17
+ align-items: center;
18
+ padding: 0.5rem 1rem;
19
+ border-radius: 0.375rem;
20
+ color: white;
21
+ transition: background-color 0.2s ease, transform 0.2s ease;
22
+ }
23
+
24
+ #sidebar nav ul li a:hover {
25
+ background-color: rgba(55, 65, 81, 0.8);
26
+ transform: translateX(4px);
27
+ }
28
+
29
+ /* تنسيق قائمة المحادثات */
30
+ #conversationList {
31
+ max-height: calc(100vh - 300px); /* تحديد الارتفاع */
32
+ overflow-y: auto; /* شريط تمرير إذا لزم الأمر */
33
+ }
34
+
35
+ #conversationList li {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ padding: 0.5rem 1rem;
40
+ border-radius: 0.375rem;
41
+ color: white;
42
+ transition: background-color 0.2s ease, transform 0.2s ease;
43
+ }
44
+
45
+ #conversationList li:hover {
46
+ background-color: rgba(55, 65, 81, 0.8);
47
+ transform: translateX(4px);
48
+ }
49
+
50
+ #conversationList li span.truncate {
51
+ max-width: 150px;
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ white-space: nowrap;
55
+ }
56
+
57
+ /* تنسيق زر الحذف */
58
+ .delete-conversation-btn {
59
+ opacity: 0;
60
+ transition: opacity 0.2s ease;
61
+ }
62
+
63
+ #conversationList li:hover .delete-conversation-btn {
64
+ opacity: 1;
65
+ }
66
+
67
+ /* تنسيق زر التبديل */
68
+ #sidebarToggle {
69
+ background: none;
70
+ border: none;
71
+ cursor: pointer;
72
+ color: white;
73
+ }
74
+
75
+ /* تنسيق زر History Toggle */
76
+ #historyToggle {
77
+ display: flex;
78
+ align-items: center;
79
+ width: 100%;
80
+ padding: 0.5rem 1rem;
81
+ color: white;
82
+ background: none;
83
+ border: none;
84
+ border-radius: 0.375rem;
85
+ text-align: left;
86
+ transition: background-color 0.2s ease;
87
+ }
88
+
89
+ #historyToggle:hover {
90
+ background-color: rgba(55, 65, 81, 0.8);
91
+ }
92
+
93
+ /* تنسيق تلميح السحب */
94
+ #swipeHint {
95
+ position: fixed;
96
+ top: 50%;
97
+ left: 1rem;
98
+ z-index: 50;
99
+ animation: swipe 2s infinite; /* استخدام swipe بدل pulse */
100
+ }
101
+
102
+ #swipeHint img {
103
+ width: 32px;
104
+ height: 32px;
105
+ }
106
+
107
+ /* الرسوم المتحركة */
108
+ @keyframes swipe {
109
+ 0% { transform: translateX(0); opacity: 0.7; }
110
+ 50% { transform: translateX(10px); opacity: 1; }
111
+ 100% { transform: translateX(0); opacity: 0.7; }
112
+ }
113
+
114
+ /* التصميم المتجاوب */
115
+ @media (min-width: 768px) {
116
+ #sidebar {
117
+ transform: translateX(0); /* السايدبار مرئي دايمًا */
118
+ }
119
+ #sidebarToggle {
120
+ display: none; /* إخفاء زر التبديل على الشاشات الكبيرة */
121
+ }
122
+ #swipeHint {
123
+ display: none; /* إخفاء تلميح السحب على الشاشات الكبيرة */
124
+ }
125
+ #conversationList {
126
+ display: block; /* قائمة المحادثات مرئية دايمًا */
127
+ }
128
+ #historyToggle {
129
+ display: none; /* إخفاء زر History Toggle على الشاشات الكبيرة */
130
+ }
131
+ }
132
+
133
+ @media (max-width: 767px) {
134
+ #sidebar {
135
+ width: 80%;
136
+ max-width: 280px;
137
+ transform: translateX(-100%); /* السايدبار مخفي افتراضيًا */
138
+ }
139
+ #sidebar.open {
140
+ transform: translateX(0); /* إظهار السايدبار عند الفتح */
141
+ }
142
+ .md\:ml-64 {
143
+ margin-left: 0 !important; /* إزالة الهامش على الموبايل */
144
+ }
145
+ #conversationList {
146
+ display: none; /* إخفاء قائمة المحادثات افتراضيًا */
147
+ }
148
+ #conversationList:not(.hidden) {
149
+ display: block; /* إظهار القائمة عند الضغط على History Toggle */
150
+ }
151
+ }
static/images/generate_icons.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cairosvg
3
+
4
+ input_svg = "mg.svg"
5
+
6
+ sizes = [48, 72, 96, 128, 192, 256, 384, 512]
7
+
8
+ output_dir = "icons"
9
+ os.makedirs(output_dir, exist_ok=True)
10
+
11
+ for size in sizes:
12
+ output_path = os.path.join(output_dir, f"mg-{size}.png")
13
+ cairosvg.svg2png(url=input_svg, write_to=output_path, output_width=size, output_height=size)
14
+ print(f"[✔] Generated: {output_path}")
static/images/ic/404.html ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>404 - Page Not Found</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
9
+ </head>
10
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col">
11
+ <div class="container max-w-6xl mx-auto text-center py-12 flex-1">
12
+ <img src="https://raw.githubusercontent.com/Mark-Lasfar/MGZon/main/public/icons/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
13
+ <h1 class="text-5xl font-bold mb-4">404 - Page Not Found 😕</h1>
14
+ <p class="text-lg mb-8">Oops! Looks like you wandered into the wrong corner of the internet.</p>
15
+ <a href="/" class="inline-block bg-gradient-to-r from-orange-500 to-red-500 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition transform">Back to Home</a>
16
+ </div>
17
+ <footer class="bg-gradient-to-r from-blue-900 to-gray-900 py-12">
18
+ <div class="container max-w-6xl mx-auto text-center">
19
+ <img src="https://raw.githubusercontent.com/Mark-Lasfar/MGZon/main/public/icons/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
20
+ <p class="mb-4">Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-orange-500 hover:underline">Mark Al-Asfar</a> | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-orange-500 hover:underline">MGZon AI</a></p>
21
+ <div class="flex justify-center gap-6">
22
+ <a href="https://github.com/Mark-Lasfar/MGZon" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-github"></i></a>
23
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-twitter"></i></a>
24
+ <a href="https://www.facebook.com/people/Mark-Al-Asfar/pfbid02GMisUQ8AqWkNZjoKtWFHH1tbdHuVscN1cjcFnZWy9HkRaAsmanBfT6mhySAyqpg4l/" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-facebook"></i></a>
25
+ </div>
26
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
27
+ </div>
28
+ </footer>
29
+ <script src="/static/js/scripts.js"></script>
30
+ </body>
31
+ </html>
static/images/ic/500.html ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/500.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>500 - Server Error</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
10
+ </head>
11
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col">
12
+ <div class="container max-w-6xl mx-auto text-center py-12 flex-1">
13
+ <img src="https://raw.githubusercontent.com/Mark-Lasfar/MGZon/main/public/icons/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
14
+ <h1 class="text-5xl font-bold mb-4">500 - Server Error 😓</h1>
15
+ <p class="text-lg mb-8">Something went wrong on our end. Please try again later or contact support.</p>
16
+ <a href="/" class="inline-block bg-gradient-to-r from-orange-500 to-red-500 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition transform">Back to Home</a>
17
+ </div>
18
+ <footer class="bg-gradient-to-r from-blue-900 to-gray-900 py-12">
19
+ <div class="container max-w-6xl mx-auto text-center">
20
+ <img src="https://raw.githubusercontent.com/Mark-Lasfar/MGZon/main/public/icons/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
21
+ <p class="mb-4">Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-orange-500 hover:underline">Mark Al-Asfar</a> | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-orange-500 hover:underline">MGZon AI</a></p>
22
+ <div class="flex justify-center gap-6">
23
+ <a href="https://github.com/Mark-Lasfar/MGZon" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-github"></i></a>
24
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-twitter"></i></a>
25
+ <a href="https://www.facebook.com/people/Mark-Al-Asfar/pfbid02GMisUQ8AqWkNZjoKtWFHH1tbdHuVscN1cjcFnZWy9HkRaAsmanBfT6mhySAyqpg4l/" class="text-2xl text-white hover:text-orange-500 transition"><i class="bx bxl-facebook"></i></a>
26
+ </div>
27
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
28
+ </div>
29
+ </footer>
30
+ <script src="/static/js/scripts.js"></script>
31
+ </body>
32
+ </html>
static/images/ic/about.html ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Learn about MGZon AI, founded by Mark Al-Asfar in Alexandria, Egypt. Discover our mission, team, and achievements in AI and e-commerce.">
7
+ <meta name="keywords" content="MGZon AI, Mark Al-Asfar, AI chatbot, e-commerce, code generation, Alexandria, technology, team, achievements">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>About MGZon AI – Our Story</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="About MGZon AI – Our Story">
15
+ <meta property="og:description" content="Learn about MGZon AI, its founder Mark Al-Asfar, and our mission to innovate in AI and e-commerce.">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/about">
18
+ <meta property="og:type" content="website">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="About MGZon AI – Our Story">
22
+ <meta name="twitter:description" content="Learn about MGZon AI, its founder Mark Al-Asfar, and our mission to innovate in AI and e-commerce.">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "Organization",
29
+ "name": "MGZon AI",
30
+ "url": "https://mgzon-mgzon-app.hf.space",
31
+ "logo": "/static/images/mg.svg",
32
+ "founder": {
33
+ "@type": "Person",
34
+ "name": "Mark Al-Asfar",
35
+ "sameAs": "https://mark-elasfar.web.app/"
36
+ },
37
+ "description": "MGZon AI develops AI-powered solutions for e-commerce and coding, founded by Mark Al-Asfar from Alexandria, Egypt.",
38
+ "keywords": ["MGZon AI", "Mark Al-Asfar", "AI chatbot", "e-commerce", "code generation", "Alexandria", "technology", "team", "MGZon", "MGZon chatbot", "E-commerce chatbot", "5G", "6G", "2025 AI trends"],
39
+ "address": {
40
+ "@type": "PostalAddress",
41
+ "addressLocality": "Alexandria",
42
+ "addressCountry": "Egypt"
43
+ }
44
+ }
45
+ </script>
46
+ <!-- Tailwind (v3) -->
47
+ <script src="https://cdn.tailwindcss.com"></script>
48
+ <!-- Boxicons -->
49
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
50
+ <style>
51
+ @keyframes gradientShift {
52
+ 0% { background-position: 0% 50%; }
53
+ 50% { background-position: 100% 50%; }
54
+ 100% { background-position: 0% 50%; }
55
+ }
56
+ body {
57
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
58
+ background-size: 400% 400%;
59
+ animation: gradientShift 15s ease infinite;
60
+ font-family: system-ui, sans-serif;
61
+ }
62
+ .glass {
63
+ background: rgba(255, 255, 255, 0.07);
64
+ border-radius: 1rem;
65
+ border: 1px solid rgba(255, 255, 255, 0.12);
66
+ backdrop-filter: blur(12px);
67
+ -webkit-backdrop-filter: blur(12px);
68
+ }
69
+ .sidebar {
70
+ transition: transform 0.3s ease-in-out;
71
+ }
72
+ .sidebar.collapsed .logo {
73
+ opacity: 0;
74
+ transition: opacity 0.2s ease;
75
+ }
76
+ .main-content {
77
+ min-height: calc(100vh - 4rem);
78
+ }
79
+ .glass:hover {
80
+ transform: scale(1.05);
81
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
82
+ background: rgba(255, 255, 255, 0.15);
83
+ }
84
+ @media (max-width: 768px) {
85
+ .sidebar {
86
+ transform: translateX(-100%);
87
+ }
88
+ .sidebar.active {
89
+ transform: translateX(0);
90
+ }
91
+ }
92
+ </style>
93
+ </head>
94
+ <body class="text-white flex flex-col min-h-screen">
95
+ <!-- Mobile toggle button -->
96
+ <button id="sidebarToggle" class="md:hidden fixed top-4 left-4 z-50 p-2 text-2xl text-white rounded bg-gray-800/60 hover:bg-gray-700/80 transition" aria-label="Toggle navigation">
97
+ <i class="bx bx-menu"></i>
98
+ </button>
99
+ <!-- Sidebar -->
100
+ <aside id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-gradient-to-b from-teal-800 to-emerald-900 p-6 flex flex-col overflow-y-auto z-40">
101
+ <button id="closeSidebar" class="md:hidden text-white text-2xl absolute top-4 right-4" aria-label="Close sidebar">✕</button>
102
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="logo w-20 h-20 mx-auto mb-8 animate-pulse">
103
+ <nav class="flex flex-col gap-3">
104
+ <a href="/" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Home</a>
105
+ <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
106
+ <a href="/about" class="px-4 py-2 rounded-lg bg-emerald-600">About MGZon</a>
107
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
108
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
109
+ <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
110
+ <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>
111
+ </nav>
112
+ </aside>
113
+ <!-- Main content -->
114
+ <main class="flex-1 md:ml-64 p-6 main-content">
115
+ <section class="container max-w-6xl mx-auto">
116
+ <div class="text-center py-12">
117
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
118
+ <h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">About MGZon AI</h1>
119
+ <p class="text-lg max-w-2xl mx-auto mb-8">Founded by Mark Al-Asfar in Alexandria, Egypt, MGZon AI is revolutionizing e-commerce and coding with AI-driven solutions.</p>
120
+ </div>
121
+ <!-- Story Section -->
122
+ <section class="my-12">
123
+ <h2 class="text-3xl font-bold text-center mb-8">Our Story</h2>
124
+ <div class="glass p-6">
125
+ <p class="mb-4">MGZon AI was founded in 2023 in Alexandria, Egypt, by Mark Al-Asfar with a vision to empower developers and businesses through AI-driven solutions like MGZon Chatbot. We leverage technologies like DeepSeek, FastAPI, and 5G/6G innovations to deliver cutting-edge e-commerce and code generation tools.</p>
126
+ <p>From a small startup to a global player, we now operate warehouses in the USA, Canada, and China, serving thousands of users worldwide.</p>
127
+ </div>
128
+ </section>
129
+ <!-- Founder Section -->
130
+ <section class="my-12">
131
+ <h2 class="text-3xl font-bold text-center mb-8">Meet Our Founder</h2>
132
+ <div class="glass p-6 text-center">
133
+ <img src="/static/images/mg.svg" alt="Mark Al-Asfar" class="w-24 h-24 mx-auto mb-4 rounded-full">
134
+ <h3 class="text-xl font-semibold mb-2">Mark Al-Asfar</h3>
135
+ <p class="mb-4">A visionary developer from Alexandria, Mark Al-Asfar founded MGZon AI to bridge the gap between AI innovation and practical applications in coding and e-commerce.</p>
136
+ <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Learn More About Mark</a>
137
+ </div>
138
+ </section>
139
+ <!-- Team Section -->
140
+ <section class="my-12">
141
+ <h2 class="text-3xl font-bold text-center mb-8">Our Team</h2>
142
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
143
+ <div class="glass p-6 text-center">
144
+ <img src="https://via.placeholder.com/96" alt="Team Member" class="w-24 h-24 mx-auto mb-4 rounded-full">
145
+ <h3 class="text-xl font-semibold mb-2">Hager Mohamed</h3>
146
+ <p class="mb-4">Lead AI Engineer, specializing in machine learning and chatbot development.</p>
147
+ </div>
148
+ <div class="glass p-6 text-center">
149
+ <img src="https://via.placeholder.com/96" alt="Team Member" class="w-24 h-24 mx-auto mb-4 rounded-full">
150
+ <h3 class="text-xl font-semibold mb-2">Ahmed Khaled</h3>
151
+ <p class="mb-4">E-commerce Specialist, driving innovative solutions for online businesses.</p>
152
+ </div>
153
+ </div>
154
+ </section>
155
+ <!-- Achievements Section -->
156
+ <section class="my-12">
157
+ <h2 class="text-3xl font-bold text-center mb-8">Our Achievements</h2>
158
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
159
+ <div class="glass p-6">
160
+ <h3 class="text-xl font-semibold mb-2">Global Reach</h3>
161
+ <p>Serving over 10,000 users across 50 countries with our AI tools.</p>
162
+ </div>
163
+ <div class="glass p-6">
164
+ <h3 class="text-xl font-semibold mb-2">Award-Winning Innovation</h3>
165
+ <p>Recognized as a top AI startup in MENA by TechCrunch in 2024.</p>
166
+ </div>
167
+ </div>
168
+ </section>
169
+ <!-- Mission Section -->
170
+ <section class="my-12">
171
+ <h2 class="text-3xl font-bold text-center mb-8">Our Mission</h2>
172
+ <div class="glass p-6">
173
+ <p>At MGZon AI, we aim to make AI accessible to developers and businesses, simplifying coding and enhancing e-commerce with cutting-edge technology.</p>
174
+ </div>
175
+ </section>
176
+ </section>
177
+ </main>
178
+ <!-- Footer -->
179
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
180
+ <div class="container max-w-6xl mx-auto text-center">
181
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
182
+ <p class="mb-4">
183
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
184
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
185
+ </p>
186
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
187
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
188
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
189
+ <h4 class="font-semibold mb-1">Email Us</h4>
190
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
191
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
192
+ <p>Reach out to our support team for any inquiries or assistance.</p>
193
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
194
+ </div>
195
+ </div>
196
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
197
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
198
+ <h4 class="font-semibold mb-1">Phone Support</h4>
199
+ <p>+1-800-123-4567</p>
200
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
201
+ <p>Contact our support team via phone for immediate assistance.</p>
202
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
203
+ </div>
204
+ </div>
205
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
206
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
207
+ <h4 class="font-semibold mb-1">Community</h4>
208
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
209
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
210
+ <p>Join our vibrant community to share ideas and collaborate.</p>
211
+ <button onclick="closeCardDetails('community')" 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('api-docs')">
215
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
216
+ <h4 class="font-semibold mb-1">API Docs</h4>
217
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
218
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
219
+ <p>Explore our API documentation for seamless integration.</p>
220
+ <button onclick="closeCardDetails('api-docs')" 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('faq')">
224
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
225
+ <h4 class="font-semibold mb-1">FAQ</h4>
226
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
227
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
228
+ <p>Find answers to common questions in our FAQ section.</p>
229
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
230
+ </div>
231
+ </div>
232
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
233
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
234
+ <h4 class="font-semibold mb-1">Documentation</h4>
235
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
236
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
237
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
238
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ <div class="flex justify-center gap-6 mt-6">
243
+ <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>
244
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
245
+ <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>
246
+ </div>
247
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
248
+ </div>
249
+ </footer>
250
+ <script>
251
+ const sidebar = document.getElementById('sidebar');
252
+ const toggleBtn = document.getElementById('sidebarToggle');
253
+ const closeSidebarBtn = document.getElementById('closeSidebar');
254
+ toggleBtn.addEventListener('click', () => {
255
+ sidebar.classList.toggle('active');
256
+ sidebar.classList.toggle('-translate-x-full');
257
+ sidebar.classList.toggle('translate-x-0');
258
+ });
259
+ closeSidebarBtn.addEventListener('click', () => {
260
+ sidebar.classList.remove('active');
261
+ sidebar.classList.add('-translate-x-full');
262
+ sidebar.classList.remove('translate-x-0');
263
+ });
264
+ document.addEventListener('click', (e) => {
265
+ if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
266
+ sidebar.classList.remove('active');
267
+ sidebar.classList.add('-translate-x-full');
268
+ sidebar.classList.remove('translate-x-0');
269
+ }
270
+ });
271
+ function showCardDetails(cardId) {
272
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
273
+ }
274
+ function closeCardDetails(cardId) {
275
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
276
+ }
277
+ </script>
278
+ </body>
279
+ </html>
static/images/ic/blog.html ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Read the latest blog posts from MGZon AI about AI, coding, and e-commerce trends.">
7
+ <meta name="keywords" content="MGZon AI, blog, AI trends, code generation, e-commerce, Mark Al-Asfar">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>Blog - MGZon AI</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="Blog - MGZon AI">
15
+ <meta property="og:description" content="Read the latest blog posts from MGZon AI about AI, coding, and e-commerce trends.">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/blog">
18
+ <meta property="og:type" content="website">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="Blog - MGZon AI">
22
+ <meta name="twitter:description" content="Read the latest blog posts from MGZon AI about AI, coding, and e-commerce trends.">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "Blog",
29
+ "name": "MGZon AI Blog",
30
+ "url": "https://mgzon-mgzon-app.hf.space/blog",
31
+ "description": "Read the latest blog posts from MGZon AI about AI, coding, and e-commerce trends.",
32
+ "keywords": ["MGZon Chatbot", "blog", "AI trends", "code generation", "e-commerce", "Mark Al-Asfar", "MGZon", "MGZon AI", "AI chatbot", "Code generation bot", "E-commerce chatbot", "Python AI chatbot", "AI for coding", "E-commerce automation", "2025 AI trends", "chatgpt", "grok", "deepseek", "text generation"],
33
+ "isPartOf": {
34
+ "@type": "WebSite",
35
+ "name": "MGZon Chatbot",
36
+ "url": "https://mgzon-mgzon-app.hf.space/"
37
+ }
38
+ }
39
+ </script>
40
+ <!-- Tailwind (v3) -->
41
+ <script src="https://cdn.tailwindcss.com"></script>
42
+ <!-- Boxicons -->
43
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
44
+ <style>
45
+ @keyframes gradientShift {
46
+ 0% { background-position: 0% 50%; }
47
+ 50% { background-position: 100% 50%; }
48
+ 100% { background-position: 0% 50%; }
49
+ }
50
+ body {
51
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
52
+ background-size: 400% 400%;
53
+ animation: gradientShift 15s ease infinite;
54
+ font-family: system-ui, sans-serif;
55
+ }
56
+ .glass {
57
+ background: rgba(255, 255, 255, 0.07);
58
+ border-radius: 1rem;
59
+ border: 1px solid rgba(255, 255, 255, 0.12);
60
+ backdrop-filter: blur(12px);
61
+ -webkit-backdrop-filter: blur(12px);
62
+ }
63
+ .sidebar {
64
+ transition: transform 0.3s ease-in-out;
65
+ }
66
+ .sidebar.collapsed .logo {
67
+ opacity: 0;
68
+ transition: opacity 0.2s ease;
69
+ }
70
+ .main-content {
71
+ min-height: calc(100vh - 4rem);
72
+ }
73
+ .glass:hover {
74
+ transform: scale(1.05);
75
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
76
+ background: rgba(255, 255, 255, 0.15);
77
+ }
78
+ @media (max-width: 768px) {
79
+ .sidebar {
80
+ transform: translateX(-100%);
81
+ }
82
+ .sidebar.active {
83
+ transform: translateX(0);
84
+ }
85
+ }
86
+ </style>
87
+ </head>
88
+ <body class="text-white flex flex-col min-h-screen">
89
+ <!-- Mobile toggle button -->
90
+ <button id="sidebarToggle" class="md:hidden fixed top-4 left-4 z-50 p-2 text-2xl text-white rounded bg-gray-800/60 hover:bg-gray-700/80 transition" aria-label="Toggle navigation">
91
+ <i class="bx bx-menu"></i>
92
+ </button>
93
+ <!-- Sidebar -->
94
+ <aside id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-gradient-to-b from-teal-800 to-emerald-900 p-6 flex flex-col overflow-y-auto z-40">
95
+ <button id="closeSidebar" class="md:hidden text-white text-2xl absolute top-4 right-4" aria-label="Close sidebar">✕</button>
96
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
97
+ <nav class="flex flex-col gap-3">
98
+ <a href="/" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Home</a>
99
+ <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
100
+ <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
101
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
102
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
103
+ <a href="/blog" class="px-4 py-2 rounded-lg bg-emerald-600">Blog</a>
104
+ <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
105
+ <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>
106
+ </nav>
107
+ </aside>
108
+ <!-- Main content -->
109
+ <main class="flex-1 md:ml-64 p-6 main-content">
110
+ <section class="container max-w-6xl mx-auto">
111
+ <div class="text-center py-12">
112
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
113
+ <h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">
114
+ MGZon AI Blog
115
+ </h1>
116
+ <p class="text-lg max-w-2xl mx-auto mb-8">
117
+ Stay updated with the latest trends in AI, coding, and e-commerce from MGZon AI. Explore topics like text generation, Python AI chatbots, and 2025 AI trends with insights from Mark Al-Asfar.
118
+ </p>
119
+ </div>
120
+ <!-- Blog Posts -->
121
+ <section class="my-12">
122
+ <h2 class="text-3xl font-bold text-center mb-8">Latest Posts</h2>
123
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
124
+ {% for post in posts %}
125
+ <div class="glass p-6">
126
+ <h3 class="text-xl font-semibold mb-2">{{ post.title }}</h3>
127
+ <p class="mb-4">{{ post.content | truncate(100) }}</p>
128
+ <p class="text-sm text-gray-300">By {{ post.author }} | {{ post.date }}</p>
129
+ <a href="/blog/{{ post.id }}" class="text-emerald-300 hover:underline">Read More →</a>
130
+ </div>
131
+ {% endfor %}
132
+ </div>
133
+ </section>
134
+ </section>
135
+ </main>
136
+ <!-- Footer -->
137
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
138
+ <div class="container max-w-6xl mx-auto text-center">
139
+ <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">
140
+ <p class="mb-4">
141
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
142
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
143
+ </p>
144
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
145
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
146
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
147
+ <h4 class="font-semibold mb-1">Email Us</h4>
148
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
149
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
150
+ <p>Reach out to our support team for any inquiries or assistance.</p>
151
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
152
+ </div>
153
+ </div>
154
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
155
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
156
+ <h4 class="font-semibold mb-1">Phone Support</h4>
157
+ <p>+1-800-123-4567</p>
158
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
159
+ <p>Contact our support team via phone for immediate assistance.</p>
160
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
161
+ </div>
162
+ </div>
163
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
164
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
165
+ <h4 class="font-semibold mb-1">Community</h4>
166
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
167
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
168
+ <p>Join our vibrant community to share ideas and collaborate.</p>
169
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
170
+ </div>
171
+ </div>
172
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
173
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
174
+ <h4 class="font-semibold mb-1">API Docs</h4>
175
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
176
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
177
+ <p>Explore our API documentation for seamless integration.</p>
178
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
179
+ </div>
180
+ </div>
181
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
182
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
183
+ <h4 class="font-semibold mb-1">FAQ</h4>
184
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
185
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
186
+ <p>Find answers to common questions in our FAQ section.</p>
187
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
188
+ </div>
189
+ </div>
190
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
191
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
192
+ <h4 class="font-semibold mb-1">Documentation</h4>
193
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
194
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
195
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
196
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ <div class="flex justify-center gap-6 mt-6">
201
+ <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>
202
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
203
+ <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>
204
+ </div>
205
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
206
+ </div>
207
+ </footer>
208
+ <script>
209
+ const sidebar = document.getElementById('sidebar');
210
+ const toggleBtn = document.getElementById('sidebarToggle');
211
+ const closeSidebarBtn = document.getElementById('closeSidebar');
212
+ toggleBtn.addEventListener('click', () => {
213
+ sidebar.classList.toggle('active');
214
+ sidebar.classList.toggle('-translate-x-full');
215
+ sidebar.classList.toggle('translate-x-0');
216
+ });
217
+ closeSidebarBtn.addEventListener('click', () => {
218
+ sidebar.classList.remove('active');
219
+ sidebar.classList.add('-translate-x-full');
220
+ sidebar.classList.remove('translate-x-0');
221
+ });
222
+ document.addEventListener('click', (e) => {
223
+ if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
224
+ sidebar.classList.remove('active');
225
+ sidebar.classList.add('-translate-x-full');
226
+ sidebar.classList.remove('translate-x-0');
227
+ }
228
+ });
229
+ function showCardDetails(cardId) {
230
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
231
+ }
232
+ function closeCardDetails(cardId) {
233
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
234
+ }
235
+ </script>
236
+ </body>
237
+ </html>
static/images/ic/blog_post.html ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="{{ post.title }} - Read about AI, coding, and e-commerce trends from MGZon AI by Mark Al-Asfar.">
7
+ <meta name="keywords" content="MGZon Chatbot, MGZon AI, blog, AI trends, code generation, e-commerce, Mark Al-Asfar, AI chatbot, code generation bot, e-commerce chatbot, Python AI chatbot, AI for coding, e-commerce automation, 2025 AI trends, chatgpt, grok, deepseek, text generation">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>{{ post.title }} - MGZon AI</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="{{ post.title }} - MGZon AI">
15
+ <meta property="og:description" content="{{ post.content | truncate(150) }}">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/blog/{{ post.id }}">
18
+ <meta property="og:type" content="article">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="{{ post.title }} - MGZon AI">
22
+ <meta name="twitter:description" content="{{ post.content | truncate(150) }}">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "BlogPosting",
29
+ "headline": "{{ post.title }}",
30
+ "url": "https://mgzon-mgzon-app.hf.space/blog/{{ post.id }}",
31
+ "description": "{{ post.content | truncate(150) }}",
32
+ "keywords": ["MGZon Chatbot", "MGZon AI", "blog", "AI trends", "code generation", "e-commerce", "Mark Al-Asfar", "AI chatbot", "code generation bot", "e-commerce chatbot", "Python AI chatbot", "AI for coding", "e-commerce automation", "2025 AI trends", "chatgpt", "grok", "deepseek", "text generation"],
33
+ "author": {
34
+ "@type": "Person",
35
+ "name": "Mark Al-Asfar",
36
+ "url": "https://mark-elasfar.web.app/"
37
+ },
38
+ "datePublished": "{{ post.date }}",
39
+ "publisher": {
40
+ "@type": "Organization",
41
+ "name": "MGZon AI",
42
+ "logo": {
43
+ "@type": "ImageObject",
44
+ "url": "/static/images/mg.svg"
45
+ }
46
+ },
47
+ "isPartOf": {
48
+ "@type": "WebSite",
49
+ "name": "MGZon Chatbot",
50
+ "url": "https://mgzon-mgzon-app.hf.space/"
51
+ }
52
+ }
53
+ </script>
54
+ <!-- Tailwind (v3) -->
55
+ <script src="https://cdn.tailwindcss.com"></script>
56
+ <!-- Boxicons -->
57
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
58
+ <style>
59
+ @keyframes gradientShift {
60
+ 0% { background-position: 0% 50%; }
61
+ 50% { background-position: 100% 50%; }
62
+ 100% { background-position: 0% 50%; }
63
+ }
64
+ body {
65
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
66
+ background-size: 400% 400%;
67
+ animation: gradientShift 15s ease infinite;
68
+ font-family: system-ui, sans-serif;
69
+ }
70
+ .glass {
71
+ background: rgba(255, 255, 255, 0.07);
72
+ border-radius: 1rem;
73
+ border: 1px solid rgba(255, 255, 255, 0.12);
74
+ backdrop-filter: blur(12px);
75
+ -webkit-backdrop-filter: blur(12px);
76
+ }
77
+ .sidebar {
78
+ transition: transform 0.3s ease-in-out;
79
+ }
80
+ .sidebar.collapsed .logo {
81
+ opacity: 0;
82
+ transition: opacity 0.2s ease;
83
+ }
84
+ .main-content {
85
+ min-height: calc(100vh - 4rem);
86
+ }
87
+ .glass:hover {
88
+ transform: scale(1.05);
89
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
90
+ background: rgba(255, 255, 255, 0.15);
91
+ }
92
+ @media (max-width: 768px) {
93
+ .sidebar {
94
+ transform: translateX(-100%);
95
+ }
96
+ .sidebar.active {
97
+ transform: translateX(0);
98
+ }
99
+ }
100
+ .markdown-content img {
101
+ max-width: 100%;
102
+ height: auto;
103
+ margin: 1rem 0;
104
+ }
105
+ .markdown-content h2 {
106
+ font-size: 1.875rem;
107
+ font-weight: bold;
108
+ margin-top: 2rem;
109
+ margin-bottom: 1rem;
110
+ }
111
+ .markdown-content p {
112
+ margin-bottom: 1rem;
113
+ }
114
+ .markdown-content a {
115
+ color: #34d399;
116
+ text-decoration: underline;
117
+ }
118
+ .markdown-content blockquote {
119
+ border-left: 4px solid #34d399;
120
+ padding-left: 1rem;
121
+ margin: 1rem 0;
122
+ color: #d1d5db;
123
+ }
124
+ </style>
125
+ </head>
126
+ <body class="text-white flex flex-col min-h-screen">
127
+ <!-- Mobile toggle button -->
128
+ <button id="sidebarToggle" class="md:hidden fixed top-4 left-4 z-50 p-2 text-2xl text-white rounded bg-gray-800/60 hover:bg-gray-700/80 transition" aria-label="Toggle navigation">
129
+ <i class="bx bx-menu"></i>
130
+ </button>
131
+ <!-- Sidebar -->
132
+ <aside id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-gradient-to-b from-teal-800 to-emerald-900 p-6 flex flex-col overflow-y-auto z-40">
133
+ <button id="closeSidebar" class="md:hidden text-white text-2xl absolute top-4 right-4" aria-label="Close sidebar">✕</button>
134
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
135
+ <nav class="flex flex-col gap-3">
136
+ <a href="/" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Home</a>
137
+ <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
138
+ <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
139
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
140
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
141
+ <a href="/blog" class="px-4 py-2 rounded-lg bg-emerald-600">Blog</a>
142
+ <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
143
+ <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>
144
+ </nav>
145
+ </aside>
146
+ <!-- Main content -->
147
+ <main class="flex-1 md:ml-64 p-6 main-content">
148
+ <section class="container max-w-6xl mx-auto">
149
+ <div class="text-center py-12">
150
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
151
+ <h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">
152
+ {{ post.title }}
153
+ </h1>
154
+ <p class="text-sm text-gray-300">By {{ post.author }} | {{ post.date }}</p>
155
+ </div>
156
+ <!-- Blog Post Content -->
157
+ <section class="my-12 glass p-8 markdown-content">
158
+ {{ post.content | markdown | safe }}
159
+ </section>
160
+ </section>
161
+ </main>
162
+ <!-- Footer -->
163
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
164
+ <div class="container max-w-6xl mx-auto text-center">
165
+ <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">
166
+ <p class="mb-4">
167
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
168
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
169
+ </p>
170
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
171
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
172
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
173
+ <h4 class="font-semibold mb-1">Email Us</h4>
174
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
175
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
176
+ <p>Reach out to our support team for any inquiries or assistance.</p>
177
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
178
+ </div>
179
+ </div>
180
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
181
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
182
+ <h4 class="font-semibold mb-1">Phone Support</h4>
183
+ <p>+1-800-123-4567</p>
184
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
185
+ <p>Contact our support team via phone for immediate assistance.</p>
186
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
187
+ </div>
188
+ </div>
189
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
190
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
191
+ <h4 class="font-semibold mb-1">Community</h4>
192
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
193
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
194
+ <p>Join our vibrant community to share ideas and collaborate.</p>
195
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
196
+ </div>
197
+ </div>
198
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
199
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
200
+ <h4 class="font-semibold mb-1">API Docs</h4>
201
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
202
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
203
+ <p>Explore our API documentation for seamless integration.</p>
204
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
205
+ </div>
206
+ </div>
207
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
208
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
209
+ <h4 class="font-semibold mb-1">FAQ</h4>
210
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
211
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
212
+ <p>Find answers to common questions in our FAQ section.</p>
213
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
214
+ </div>
215
+ </div>
216
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
217
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
218
+ <h4 class="font-semibold mb-1">Documentation</h4>
219
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
220
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
221
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
222
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ <div class="flex justify-center gap-6 mt-6">
227
+ <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>
228
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
229
+ <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>
230
+ </div>
231
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
232
+ </div>
233
+ </footer>
234
+ <script>
235
+ const sidebar = document.getElementById('sidebar');
236
+ const toggleBtn = document.getElementById('sidebarToggle');
237
+ const closeSidebarBtn = document.getElementById('closeSidebar');
238
+ toggleBtn.addEventListener('click', () => {
239
+ sidebar.classList.toggle('active');
240
+ sidebar.classList.toggle('-translate-x-full');
241
+ sidebar.classList.toggle('translate-x-0');
242
+ });
243
+ closeSidebarBtn.addEventListener('click', () => {
244
+ sidebar.classList.remove('active');
245
+ sidebar.classList.add('-translate-x-full');
246
+ sidebar.classList.remove('translate-x-0');
247
+ });
248
+ document.addEventListener('click', (e) => {
249
+ if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
250
+ sidebar.classList.remove('active');
251
+ sidebar.classList.add('-translate-x-full');
252
+ sidebar.classList.remove('translate-x-0');
253
+ }
254
+ });
255
+ function showCardDetails(cardId) {
256
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
257
+ }
258
+ function closeCardDetails(cardId) {
259
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
260
+ }
261
+ </script>
262
+ </body>
263
+ </html>
static/images/ic/chat.html ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark-theme">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
+ <meta name="description" content="Chat with MGZon Chatbot, an AI-powered tool for coding, analysis, and e-commerce queries. Supports text, image, and audio inputs." />
7
+ <meta name="keywords" content="MGZon Chatbot, AI chatbot, code generation, DeepSeek, Gradio, FastAPI, e-commerce, programming, Mark Al-Asfar" />
8
+ <meta name="author" content="Mark Al-Asfar" />
9
+ <meta name="robots" content="index, follow" />
10
+ <title>MGZon Chatbot – AI Assistant</title>
11
+ <link rel="sitemap" type="application/xml" title="Sitemap" href="/sitemap.xml" />
12
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
13
+ <link rel="apple-touch-icon" href="/static/images/mg.svg" />
14
+ <!-- Open Graph -->
15
+ <meta property="og:title" content="MGZon Chatbot – AI Assistant" />
16
+ <meta property="og:description" content="Chat with MGZon Chatbot for coding, analysis, and e-commerce queries with text, image, and audio support." />
17
+ <meta property="og:image" content="/static/images/mg.svg" />
18
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/chat" />
19
+ <meta property="og:type" content="website" />
20
+ <!-- Twitter Card -->
21
+ <meta name="twitter:card" content="summary_large_image" />
22
+ <meta name="twitter:title" content="MGZon Chatbot – AI Assistant" />
23
+ <meta name="twitter:description" content="Chat with MGZon Chatbot for coding, analysis, and e-commerce queries with text, image, and audio support." />
24
+ <meta name="twitter:image" content="/static/images/mg.svg" />
25
+ <!-- JSON-LD -->
26
+ <script type="application/ld+json">
27
+ {
28
+ "@context": "https://schema.org",
29
+ "@type": "WebPage",
30
+ "name": "MGZon Chatbot",
31
+ "url": "https://mgzon-mgzon-app.hf.space/chat",
32
+ "description": "An AI-powered chatbot for coding, analysis, and e-commerce queries."
33
+ }
34
+ </script>
35
+ <!-- Fonts & Styles -->
36
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
37
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet" />
38
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
39
+ <script src="https://cdn.tailwindcss.com"></script>
40
+ <!-- Markdown & Syntax Highlight -->
41
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
42
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" />
43
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
44
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
45
+ <!-- Animations -->
46
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.js"></script>
47
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/aos.css" />
48
+ <!-- Carousel -->
49
+ <script src="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/js/splide.min.js"></script>
50
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/css/splide.min.css" />
51
+ <!-- Smooth Scroll -->
52
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.js"></script>
53
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.css" />
54
+ <!-- Hammer.js for touch gestures -->
55
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/hammer.min.js"></script>
56
+ <!-- Project-Specific Styles -->
57
+ <link rel="stylesheet" href="/static/css/animation/style.css" />
58
+ <link rel="stylesheet" href="/static/css/button.css" />
59
+ <link rel="stylesheet" href="/static/css/chat/bubble.css" />
60
+ <link rel="stylesheet" href="/static/css/chat/markdown.css" />
61
+ <link rel="stylesheet" href="/static/css/chat/style.css" />
62
+ <link rel="stylesheet" href="/static/css/header.css" />
63
+ <link rel="stylesheet" href="/static/css/icon.css" />
64
+ <link rel="stylesheet" href="/static/css/input.css" />
65
+ <link rel="stylesheet" href="/static/css/logo.css" />
66
+ <link rel="stylesheet" href="/static/css/prompts.css" />
67
+ <link rel="stylesheet" href="/static/css/screen/1200.css" />
68
+ <link rel="stylesheet" href="/static/css/screen/2000.css" />
69
+ <link rel="stylesheet" href="/static/css/screen/320.css" />
70
+ <link rel="stylesheet" href="/static/css/screen/360.css" />
71
+ <link rel="stylesheet" href="/static/css/screen/3840.css" />
72
+ <link rel="stylesheet" href="/static/css/screen/480.css" />
73
+ <link rel="stylesheet" href="/static/css/screen/720.css" />
74
+ <link rel="stylesheet" href="/static/css/screen/7680.css" />
75
+ <link rel="stylesheet" href="/static/css/screen/common.css" />
76
+ <link rel="stylesheet" href="/static/css/style.css" />
77
+ <link rel="stylesheet" href="/static/css/webkit.css" />
78
+ <link rel="stylesheet" href="/static/css/sidebar.css" />
79
+ </head>
80
+ <body class="min-h-screen flex flex-col bg-gradient-to-br from-gray-900 via-teal-900 to-emerald-900">
81
+ <!-- Sidebar -->
82
+ <aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-gray-800/90 backdrop-blur-md transform -translate-x-full md:translate-x-0 transition-transform duration-300 ease-in-out z-50">
83
+ <div class="flex items-center justify-between p-4 border-b border-gray-700">
84
+ <div class="flex items-center">
85
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-10 h-10 mr-2 animate-pulse" />
86
+ <h2 class="text-lg font-bold text-white">MGZon</h2>
87
+ </div>
88
+ <button id="sidebarToggle" class="md:hidden text-white" aria-label="Close Sidebar">
89
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
91
+ </svg>
92
+ </button>
93
+ </div>
94
+ <nav class="p-4">
95
+ <ul class="space-y-2">
96
+ <li>
97
+ <a href="/" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
98
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
99
+ <path d="M3 11.5L12 4l9 7.5M5 21V11.5h14V21"></path>
100
+ </svg>
101
+ Home
102
+ </a>
103
+ </li>
104
+ <li>
105
+ <a href="/about" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
106
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
107
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
108
+ </svg>
109
+ About
110
+ </a>
111
+ </li>
112
+ <li>
113
+ <a href="/blog" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
114
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
115
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 006 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"></path>
116
+ </svg>
117
+ Blog
118
+ </a>
119
+ </li>
120
+ <li>
121
+ <a href="/docs" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
122
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
123
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
124
+ </svg>
125
+ Docs
126
+ </a>
127
+ </li>
128
+ {% if user %}
129
+ <li>
130
+ <a href="/auth/jwt/logout" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
131
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
132
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
133
+ </svg>
134
+ Logout
135
+ </a>
136
+ </li>
137
+ {% else %}
138
+ <li>
139
+ <a href="/login" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
140
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
141
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
142
+ </svg>
143
+ Login
144
+ </a>
145
+ </li>
146
+ {% endif %}
147
+ </ul>
148
+ {% if user %}
149
+ <div class="mt-4">
150
+ <div class="flex justify-between items-center px-2 mb-2">
151
+ <h3 class="text-sm font-semibold text-white">Conversations</h3>
152
+ <button id="newConversationBtn" class="text-white hover:bg-gray-700 p-1 rounded" title="New Conversation">
153
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
154
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
155
+ </svg>
156
+ </button>
157
+ </div>
158
+ <ul id="conversationList" class="space-y-2 max-h-[calc(100vh-300px)] overflow-y-auto">
159
+ <!-- Conversations will be populated by JavaScript -->
160
+ </ul>
161
+ </div>
162
+ {% endif %}
163
+ </nav>
164
+ </aside>
165
+
166
+ <!-- Swipe Hint for Mobile -->
167
+ <div id="swipeHint" class="md:hidden fixed top-1/2 left-4 z-50 animate-pulse">
168
+ <img src="/static/images/swipe-hint.svg" alt="Swipe to open sidebar" class="w-8 h-8" />
169
+ </div>
170
+
171
+ <!-- Main Content -->
172
+ <div class="flex-1 flex flex-col max-w-screen-xl w-full mx-auto md:ml-64">
173
+ <!-- Chat Header -->
174
+ <header class="chat-header p-3 flex justify-between items-center">
175
+ <div class="chat-title text-xl font-semibold text-white">
176
+ <span id="conversationTitle">{{ conversation_title | default('MGZon AI Assistant') }}</span>
177
+ </div>
178
+ <div class="chat-controls flex gap-2">
179
+ <button id="clearBtn" class="icon-btn" aria-label="Clear All Messages" title="Clear All Messages">
180
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
181
+ <path d="M3 6h18" />
182
+ <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
183
+ <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
184
+ <path d="M10 11v6" />
185
+ <path d="M14 11v6" />
186
+ </svg>
187
+ </button>
188
+ </div>
189
+ </header>
190
+
191
+ <!-- Chat Area -->
192
+ <main class="flex-1 flex flex-col">
193
+ <div id="initialContent" class="flex flex-col items-center justify-center text-center h-full">
194
+ <div class="title mb-4 gradient-text text-3xl font-bold">
195
+ How can I help you today?
196
+ </div>
197
+ <p class="system text-gray-300 mb-4">
198
+ A versatile chatbot powered by DeepSeek, GPT-OSS, CLIP, Whisper, and TTS.<br>
199
+ Type your query, upload images/files, or hold the send button to record audio!
200
+ </p>
201
+ <!-- Prompts -->
202
+ <div class="prompts w-full max-w-md mx-auto grid gap-2">
203
+ <div class="prompt-item" data-prompt="What's the capital of France?">
204
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
205
+ <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" />
206
+ <circle cx="12" cy="12" r="4" stroke-width="2" />
207
+ </svg>
208
+ <span>What's the capital of France?</span>
209
+ </div>
210
+ <div class="prompt-item" data-prompt="Generate a Python script for a simple web server">
211
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
212
+ <path d="M4 7h16M4 12h16M4 17h16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
213
+ </svg>
214
+ <span>Generate a Python script for a simple web server</span>
215
+ </div>
216
+ <div class="prompt-item" data-prompt="Analyze this image for me">
217
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
218
+ <path d="M3 12h18M12 3v18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
219
+ </svg>
220
+ <span>Analyze this image for me</span>
221
+ </div>
222
+ <div class="prompt-item" data-prompt="Convert this text to audio">
223
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
224
+ <path d="M12 3v18M7 12h10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
225
+ </svg>
226
+ <span>Convert this text to audio</span>
227
+ </div>
228
+ </div>
229
+ <div id="messageLimitWarning" class="text-red-500 text-center hidden mt-4">
230
+ You have reached the message limit. Please <a href="/login" class="text-blue-300 underline">login</a> to continue.
231
+ </div>
232
+ </div>
233
+ <div id="chatArea" class="flex-1 hidden" aria-live="polite">
234
+ <div id="chatBox" class="flex-col" aria-live="polite"></div>
235
+ </div>
236
+ <!-- Footer Form -->
237
+ <form id="footerForm" class="flex p-4">
238
+ <div id="inputContainer" class="w-full">
239
+ <textarea id="userInput" placeholder="Ask anything..." required></textarea>
240
+ <div id="rightIconGroup" class="flex gap-2">
241
+ <button type="button" id="fileBtn" class="icon-btn" aria-label="Upload File" title="Upload File">
242
+ <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">
243
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
244
+ <path d="M14 2v6h6" />
245
+ </svg>
246
+ </button>
247
+ <input type="file" id="fileInput" accept="image/*,.mp3,.wav" style="display: none;" />
248
+ <button type="button" id="audioBtn" class="icon-btn" aria-label="Upload Audio File" title="Upload Audio File">
249
+ <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">
250
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
251
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
252
+ <path d="M12 19v4" />
253
+ </svg>
254
+ </button>
255
+ <input type="file" id="audioInput" accept="audio/*" style="display: none;" />
256
+ <button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send or Hold to Record" title="Click to Send or Hold to Record Voice">
257
+ <svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
258
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
259
+ </svg>
260
+ </button>
261
+ <button id="stopBtn" class="icon-btn" aria-label="Stop" title="Stop" style="display: none;">
262
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
263
+ <rect x="6" y="6" width="12" height="12" rx="2" fill="white" />
264
+ </svg>
265
+ </button>
266
+ </div>
267
+ </div>
268
+ <div id="filePreview" class="upload-preview" style="display: none;"></div>
269
+ <div id="audioPreview" class="audio-preview" style="display: none;"></div>
270
+ </form>
271
+ </main>
272
+
273
+ <!-- Copyright -->
274
+ <div class="text-center text-xs text-gray-400 py-2">
275
+ © 2025 Mark Al-Asfar & MGZon AI. All rights reserved.
276
+ </div>
277
+ </div>
278
+
279
+ <!-- Scripts -->
280
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
281
+ <script src="/static/js/chat.js"></script>
282
+ <script>
283
+ AOS.init();
284
+ // تمرير conversation_id و conversation_title إذا كانا موجودين
285
+ {% if conversation_id %}
286
+ window.conversationId = "{{ conversation_id }}";
287
+ window.conversationTitle = "{{ conversation_title }}";
288
+ if (window.loadConversation) {
289
+ window.loadConversation("{{ conversation_id }}");
290
+ }
291
+ {% endif %}
292
+ </script>
293
+ </body>
294
+ </html>
static/images/ic/docs.html ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Explore MGZon Chatbot API documentation for integrating AI-powered code generation and e-commerce tools.">
7
+ <meta name="keywords" content="MGZon Chatbot, API documentation, code generation, e-commerce, FastAPI, Mark Al-Asfar">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>API Documentation - MGZon Chatbot</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="API Documentation - MGZon Chatbot">
15
+ <meta property="og:description" content="Explore MGZon Chatbot API documentation for integrating AI-powered code generation and e-commerce tools.">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/docs">
18
+ <meta property="og:type" content="website">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="API Documentation - MGZon Chatbot">
22
+ <meta name="twitter:description" content="Explore MGZon Chatbot API documentation for integrating AI-powered code generation and e-commerce tools.">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "WebPage",
29
+ "name": "API Documentation - MGZon Chatbot",
30
+ "url": "https://mgzon-mgzon-app.hf.space/docs",
31
+ "description": "Explore MGZon Chatbot API documentation for integrating AI-powered code generation and e-commerce tools.",
32
+ "keywords": ["MGZon Chatbot", "API documentation", "code generation", "e-commerce", "FastAPI", "Mark Al-Asfar", "MGZon", "MGZon AI", "AI chatbot", "Code generation bot", "Python AI chatbot", "FastAPI integration", "AI for coding", "AI developer tools", "text generation"],
33
+ "isPartOf": {
34
+ "@type": "WebSite",
35
+ "name": "MGZon Chatbot",
36
+ "url": "https://mgzon-mgzon-app.hf.space/"
37
+ }
38
+ }
39
+ </script>
40
+ <!-- Tailwind (v3) -->
41
+ <script src="https://cdn.tailwindcss.com"></script>
42
+ <!-- Boxicons -->
43
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
44
+ <!-- Prism (for code highlighting) -->
45
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/themes/prism.min.css" rel="stylesheet">
46
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/prism.min.js"></script>
47
+ <style>
48
+ @keyframes gradientShift {
49
+ 0% { background-position: 0% 50%; }
50
+ 50% { background-position: 100% 50%; }
51
+ 100% { background-position: 0% 50%; }
52
+ }
53
+ body {
54
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
55
+ background-size: 400% 400%;
56
+ animation: gradientShift 15s ease infinite;
57
+ font-family: system-ui, sans-serif;
58
+ }
59
+ .glass {
60
+ background: rgba(255, 255, 255, 0.07);
61
+ border-radius: 1rem;
62
+ border: 1px solid rgba(255, 255, 255, 0.12);
63
+ backdrop-filter: blur(12px);
64
+ -webkit-backdrop-filter: blur(12px);
65
+ }
66
+ .sidebar {
67
+ transition: transform 0.3s ease-in-out;
68
+ }
69
+ .sidebar.collapsed .logo {
70
+ opacity: 0;
71
+ transition: opacity 0.2s ease;
72
+ }
73
+ .main-content {
74
+ min-height: calc(100vh - 4rem);
75
+ }
76
+ .glass:hover {
77
+ transform: scale(1.05);
78
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
79
+ background: rgba(255, 255, 255, 0.15);
80
+ }
81
+ @media (max-width: 768px) {
82
+ .sidebar {
83
+ transform: translateX(-100%);
84
+ }
85
+ .sidebar.active {
86
+ transform: translateX(0);
87
+ }
88
+ }
89
+ </style>
90
+ </head>
91
+ <body class="text-white flex flex-col min-h-screen">
92
+ <!-- Mobile toggle button -->
93
+ <button id="sidebarToggle" class="md:hidden fixed top-4 left-4 z-50 p-2 text-2xl text-white rounded bg-gray-800/60 hover:bg-gray-700/80 transition" aria-label="Toggle navigation">
94
+ <i class="bx bx-menu"></i>
95
+ </button>
96
+ <!-- Sidebar -->
97
+ <aside id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-gradient-to-b from-teal-800 to-emerald-900 p-6 flex flex-col overflow-y-auto z-40">
98
+ <button id="closeSidebar" class="md:hidden text-white text-2xl absolute top-4 right-4" aria-label="Close sidebar">✕</button>
99
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="logo w-20 h-20 mx-auto mb-8 animate-pulse">
100
+ <nav class="flex flex-col gap-3">
101
+ <a href="/" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Home</a>
102
+ <a href="/docs" class="px-4 py-2 rounded-lg bg-emerald-600">API Documentation</a>
103
+ <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
104
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
105
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
106
+ <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
107
+ <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>
108
+ </nav>
109
+ </aside>
110
+ <!-- Main content -->
111
+ <main class="flex-1 md:ml-64 p-6 main-content">
112
+ <section class="container max-w-6xl mx-auto">
113
+ <div class="text-center py-12">
114
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
115
+ <h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">About MGZon AI by Mark Al-Asfar</h1>
116
+ MGZon Chatbot API Documentation
117
+ </h1>
118
+ <p class="text-lg max-w-2xl mx-auto mb-8">
119
+ Integrate MGZon Chatbot's AI-powered features into your applications. Learn how to use our APIs for code generation, web search, and e-commerce solutions.
120
+ </p>
121
+ <p>Explore the MGZon Chatbot API for seamless integration of AI-powered code generation, text generation, and e-commerce automation. Built by Mark Al-Asfar using FastAPI and Hugging Face AI.</p>
122
+ <a href="https://x.ai/api" target="_blank" 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">
123
+ Get API Key <i class="bx bx-key ml-2"></i>
124
+ </a>
125
+ </div>
126
+ <!-- API Overview -->
127
+ <section class="my-12">
128
+ <h2 class="text-3xl font-bold text-center mb-8">API Overview</h2>
129
+ <div class="glass p-6">
130
+ <p class="mb-4">The MGZon Chatbot API provides endpoints for code generation, real-time web search, and e-commerce functionalities. Built with FastAPI, it offers secure and scalable integration.</p>
131
+ <p>Base URL: <code class="bg-gray-800/60 p-1 rounded">https://mgzon-mgzon-app.hf.space/api</code></p>
132
+ </div>
133
+ </section>
134
+ <!-- Authentication -->
135
+ <section class="my-12">
136
+ <h2 class="text-3xl font-bold text-center mb-8">Authentication</h2>
137
+ <div class="glass p-6">
138
+ <p class="mb-4">Use JWT tokens for authentication. Register or login to obtain a token.</p>
139
+ <pre><code class="language-bash">
140
+ curl -X POST "https://mgzon-mgzon-app.hf.space/auth/jwt/login" \
141
+ -H "Content-Type: application/x-www-form-urlencoded" \
142
+ -d "username=your_email&password=your_password"
143
+ </code></pre>
144
+ </div>
145
+ </section>
146
+ <!-- Endpoints -->
147
+ <section class="my-12">
148
+ <h2 class="text-3xl font-bold text-center mb-8">Endpoints</h2>
149
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
150
+ <div class="glass p-6">
151
+ <h3 class="text-xl font-semibold mb-2">/chat (POST)</h3>
152
+ <p class="mb-4">Interact with the AI chatbot for code generation or queries.</p>
153
+ <pre><code class="language-bash">
154
+ curl -X POST "https://mgzon-mgzon-app.hf.space/chat" \
155
+ -H "Authorization: Bearer your_token" \
156
+ -H "Content-Type: application/json" \
157
+ -d '{"message": "Generate a Python function"}'
158
+ </code></pre>
159
+ </div>
160
+ <div class="glass p-6">
161
+ <h3 class="text-xl font-semibold mb-2">/web_search (POST)</h3>
162
+ <p class="mb-4">Perform real-time web searches.</p>
163
+ <pre><code class="language-bash">
164
+ curl -X POST "https://mgzon-mgzon-app.hf.space/web_search" \
165
+ -H "Authorization: Bearer your_token" \
166
+ -H "Content-Type: application/json" \
167
+ -d '{"query": "AI trends 2025"}'
168
+ </code></pre>
169
+ </div>
170
+ </div>
171
+ </section>
172
+ <!-- Getting Started -->
173
+ <section class="my-12">
174
+ <h2 class="text-3xl font-bold text-center mb-8">Getting Started</h2>
175
+ <div class="glass p-6">
176
+ <p class="mb-4">1. Register at <a href="/register" class="text-emerald-300 hover:underline">/register</a>.</p>
177
+ <p class="mb-4">2. Obtain your API key from <a href="https://x.ai/api" target="_blank" class="text-emerald-300 hover:underline">xAI API</a>.</p>
178
+ <p>3. Use the endpoints above to integrate MGZon Chatbot into your application.</p>
179
+ </div>
180
+ </section>
181
+ </section>
182
+ </main>
183
+ <!-- Footer -->
184
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
185
+ <div class="container max-w-6xl mx-auto text-center">
186
+ <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">
187
+ <p class="mb-4">
188
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
189
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
190
+ </p>
191
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
192
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
193
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
194
+ <h4 class="font-semibold mb-1">Email Us</h4>
195
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
196
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
197
+ <p>Reach out to our support team for any inquiries or assistance.</p>
198
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
199
+ </div>
200
+ </div>
201
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
202
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
203
+ <h4 class="font-semibold mb-1">Phone Support</h4>
204
+ <p>+1-800-123-4567</p>
205
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
206
+ <p>Contact our support team via phone for immediate assistance.</p>
207
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
208
+ </div>
209
+ </div>
210
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
211
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
212
+ <h4 class="font-semibold mb-1">Community</h4>
213
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
214
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
215
+ <p>Join our vibrant community to share ideas and collaborate.</p>
216
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
217
+ </div>
218
+ </div>
219
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
220
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
221
+ <h4 class="font-semibold mb-1">API Docs</h4>
222
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
223
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
224
+ <p>Explore our API documentation for seamless integration.</p>
225
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
226
+ </div>
227
+ </div>
228
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
229
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
230
+ <h4 class="font-semibold mb-1">FAQ</h4>
231
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
232
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
233
+ <p>Find answers to common questions in our FAQ section.</p>
234
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
235
+ </div>
236
+ </div>
237
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
238
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
239
+ <h4 class="font-semibold mb-1">Documentation</h4>
240
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
241
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
242
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
243
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
244
+ </div>
245
+ </div>
246
+ </div>
247
+ <div class="flex justify-center gap-6 mt-6">
248
+ <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>
249
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
250
+ <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>
251
+ </div>
252
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
253
+ </div>
254
+ </footer>
255
+ <script>
256
+ const sidebar = document.getElementById('sidebar');
257
+ const toggleBtn = document.getElementById('sidebarToggle');
258
+ const closeSidebarBtn = document.getElementById('closeSidebar');
259
+ toggleBtn.addEventListener('click', () => {
260
+ sidebar.classList.toggle('active');
261
+ sidebar.classList.toggle('-translate-x-full');
262
+ sidebar.classList.toggle('translate-x-0');
263
+ });
264
+ closeSidebarBtn.addEventListener('click', () => {
265
+ sidebar.classList.remove('active');
266
+ sidebar.classList.add('-translate-x-full');
267
+ sidebar.classList.remove('translate-x-0');
268
+ });
269
+ document.addEventListener('click', (e) => {
270
+ if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
271
+ sidebar.classList.remove('active');
272
+ sidebar.classList.add('-translate-x-full');
273
+ sidebar.classList.remove('translate-x-0');
274
+ }
275
+ });
276
+ function showCardDetails(cardId) {
277
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
278
+ }
279
+ function closeCardDetails(cardId) {
280
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
281
+ }
282
+ </script>
283
+ </body>
284
+ </html>
static/images/ic/index.html ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
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
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
14
+ <!-- Open Graph -->
15
+ <meta property="og:title" content="MGZon Chatbot – AI-Powered Solutions">
16
+ <meta property="og:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
17
+ <meta property="og:image" content="/static/images/mg.svg">
18
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/">
19
+ <meta property="og:type" content="website">
20
+ <!-- Twitter Card -->
21
+ <meta name="twitter:card" content="summary_large_image">
22
+ <meta name="twitter:title" content="MGZon Chatbot – AI-Powered Solutions">
23
+ <meta name="twitter:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
24
+ <meta name="twitter:image" content="/static/images/mg.svg">
25
+ <!-- JSON-LD -->
26
+ <script type="application/ld+json">
27
+ {
28
+ "@context": "https://schema.org",
29
+ "@type": "WebSite",
30
+ "name": "MGZon Chatbot",
31
+ "url": "https://mgzon-mgzon-app.hf.space/",
32
+ "description": "MGZon Chatbot by Mark Al-Asfar: Your AI assistant for code generation, real-time web search, and e-commerce solutions.",
33
+ "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"],
34
+ "potentialAction": {
35
+ "@type": "SearchAction",
36
+ "target": "https://mgzon-mgzon-app.hf.space/?q={search_term_string}",
37
+ "query-input": "required name=search_term_string"
38
+ },
39
+ "publisher": {
40
+ "@type": "Organization",
41
+ "name": "MGZon AI",
42
+ "logo": {
43
+ "@type": "ImageObject",
44
+ "url": "/static/images/mg.svg"
45
+ }
46
+ }
47
+ }
48
+ </script>
49
+ <!-- Tailwind (v3) -->
50
+ <script src="https://cdn.tailwindcss.com"></script>
51
+ <!-- Boxicons -->
52
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
53
+ <!-- Prism (for code highlighting) -->
54
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/themes/prism.min.css" rel="stylesheet">
55
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/prism.min.js"></script>
56
+ <style>
57
+ @keyframes gradientShift {
58
+ 0% { background-position: 0% 50%; }
59
+ 50% { background-position: 100% 50%; }
60
+ 100% { background-position: 0% 50%; }
61
+ }
62
+ body {
63
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
64
+ background-size: 400% 400%;
65
+ animation: gradientShift 15s ease infinite;
66
+ font-family: system-ui, sans-serif;
67
+ }
68
+ .glass {
69
+ background: rgba(255, 255, 255, 0.07);
70
+ border-radius: 1rem;
71
+ border: 1px solid rgba(255, 255, 255, 0.12);
72
+ backdrop-filter: blur(12px);
73
+ -webkit-backdrop-filter: blur(12px);
74
+ }
75
+ .sidebar {
76
+ transition: transform 0.3s ease-in-out;
77
+ }
78
+ .sidebar.collapsed .logo {
79
+ opacity: 0;
80
+ transition: opacity 0.2s ease;
81
+ }
82
+ .main-content {
83
+ min-height: calc(100vh - 4rem);
84
+ }
85
+ .loading {
86
+ display: inline-block;
87
+ width: 1rem;
88
+ height: 1rem;
89
+ border: 2px solid currentColor;
90
+ border-top-color: transparent;
91
+ border-radius: 50%;
92
+ animation: spin 0.8s linear infinite;
93
+ margin-left: 0.5rem;
94
+ }
95
+ @keyframes spin {
96
+ to { transform: rotate(360deg); }
97
+ }
98
+ .glass:hover {
99
+ transform: scale(1.05);
100
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
101
+ background: rgba(255, 255, 255, 0.15);
102
+ }
103
+ @media (max-width: 768px) {
104
+ .sidebar {
105
+ transform: translateX(-100%);
106
+ }
107
+ .sidebar.active {
108
+ transform: translateX(0);
109
+ }
110
+ }
111
+ </style>
112
+ </head>
113
+ <body class="text-white flex flex-col min-h-screen">
114
+ <!-- Mobile toggle button -->
115
+ <button id="sidebarToggle" class="md:hidden fixed top-4 left-4 z-50 p-2 text-2xl text-white rounded bg-gray-800/60 hover:bg-gray-700/80 transition" aria-label="Toggle navigation">
116
+ <i class="bx bx-menu"></i>
117
+ </button>
118
+ <!-- Sidebar -->
119
+ <aside id="sidebar" class="sidebar fixed inset-y-0 left-0 w-64 bg-gradient-to-b from-teal-800 to-emerald-900 p-6 flex flex-col overflow-y-auto z-40">
120
+ <button id="closeSidebar" class="md:hidden text-white text-2xl absolute top-4 right-4" aria-label="Close sidebar">✕</button>
121
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="logo w-20 h-20 mx-auto mb-8 animate-pulse">
122
+ <nav class="flex flex-col gap-3">
123
+ <a href="/" class="px-4 py-2 rounded-lg bg-emerald-600">Home</a>
124
+ <a href="/docs" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">API Documentation</a>
125
+ <a href="/about" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">About MGZon</a>
126
+ <a href="/login" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Login</a>
127
+ <a href="/register" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Register</a>
128
+ <a href="https://hager-zon.vercel.app/community" class="px-4 py-2 rounded-lg hover:bg-emerald-600 transition">Community</a>
129
+ <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>
130
+ </nav>
131
+ </aside>
132
+ <!-- Main content -->
133
+ <main class="flex-1 md:ml-64 p-6 main-content">
134
+ <section class="container max-w-6xl mx-auto">
135
+ <!-- Hero -->
136
+ <div class="text-center py-12">
137
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
138
+ <h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">
139
+ Welcome to MGZon Chatbot 🚀
140
+ </h1>
141
+ <p class="text-lg max-w-2xl mx-auto mb-8">
142
+ 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
143
+ United States. Ready to code smarter and shop better?
144
+ 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.
145
+ </p>
146
+ {% if user %}
147
+ <div class="flex justify-center gap-4">
148
+ <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">
149
+ Launch Chatbot <i class="bx bx-rocket ml-2"></i>
150
+ <span id="spinner" class="loading hidden"></span>
151
+ </a>
152
+ <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">
153
+ Logout <i class="bx bx-log-out ml-2"></i>
154
+ </a>
155
+ </div>
156
+ {% else %}
157
+ <div class="flex justify-center gap-4">
158
+ <a href="/login" class="inline-flex items-center bg-gradient-to-r from-blue-500 to-cyan-600 text-white px-8 py-4 rounded-full text-lg font-semibold hover:scale-105 transition-transform shadow-lg hover:shadow-xl">
159
+ Login <i class="bx bx-log-in ml-2"></i>
160
+ </a>
161
+ <a href="/register" 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">
162
+ Sign Up <i class="bx bx-user-plus ml-2"></i>
163
+ </a>
164
+ </div>
165
+ {% endif %}
166
+ </div>
167
+ <!-- Features -->
168
+ <section class="my-12">
169
+ <h2 class="text-3xl font-bold text-center mb-8">Why Choose MGZon?</h2>
170
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
171
+ <div class="glass p-6 relative group cursor-pointer" onclick="showCardDetails('code-generation')">
172
+ <button class="absolute top-2 right-2 text-gray-300 hover:text-white transition" onclick="event.stopPropagation(); closeCardDetails('code-generation')" aria-label="Close card">&times;</button>
173
+ <i class="bx bx-code-alt text-4xl text-emerald-300 mb-4"></i>
174
+ <h3 class="text-xl font-semibold mb-2">Code Generation</h3>
175
+ <p>Generate clean, well-commented code for React, Django, Flask, Node.js, and more.</p>
176
+ <div id="code-generation-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
177
+ <p>Supports multiple frameworks with examples and best practices for seamless integration.</p>
178
+ <button onclick="closeCardDetails('code-generation')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
179
+ </div>
180
+ </div>
181
+ <div class="glass p-6 relative group cursor-pointer" onclick="showCardDetails('web-search')">
182
+ <button class="absolute top-2 right-2 text-gray-300 hover:text-white transition" onclick="event.stopPropagation(); closeCardDetails('web-search')" aria-label="Close card">&times;</button>
183
+ <i class="bx bx-search text-4xl text-emerald-300 mb-4"></i>
184
+ <h3 class="text-xl font-semibold mb-2">Web Search</h3>
185
+ <p>Real-time web search for MGZon queries and industry insights.</p>
186
+ <div id="web-search-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
187
+ <p>Get up-to-date answers and insights tailored to your needs.</p>
188
+ <button onclick="closeCardDetails('web-search')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
189
+ </div>
190
+ </div>
191
+ <div class="glass p-6 relative group cursor-pointer" onclick="showCardDetails('ecommerce')">
192
+ <button class="absolute top-2 right-2 text-gray-300 hover:text-white transition" onclick="event.stopPropagation(); closeCardDetails('ecommerce')" aria-label="Close card">&times;</button>
193
+ <i class="bx bx-cart text-4xl text-emerald-300 mb-4"></i>
194
+ <h3 class="text-xl font-semibold mb-2">E-commerce Solutions</h3>
195
+ <p>AI-driven tools to optimize your online store and boost sales.</p>
196
+ <div id="ecommerce-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
197
+ <p>From product recommendations to inventory management, MGZon enhances your e-commerce experience.</p>
198
+ <button onclick="closeCardDetails('ecommerce')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </section>
203
+ <!-- News -->
204
+ <section class="my-12">
205
+ <h2 class="text-3xl font-bold text-center mb-8">Latest MGZon Updates</h2>
206
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
207
+ <div class="glass p-6">
208
+ <h3 class="text-xl font-semibold mb-2">New AI Features</h3>
209
+ <p>Explore our latest AI updates for smarter code and e-commerce tools.</p>
210
+ <a href="https://hager-zon.vercel.app/blog" target="_blank" class="text-emerald-300 hover:underline">Read More →</a>
211
+ </div>
212
+ <div class="glass p-6">
213
+ <h3 class="text-xl font-semibold mb-2">Global Expansion</h3>
214
+ <p>New warehouses in the USA, Canada, and China to serve you better.</p>
215
+ <a href="https://hager-zon.vercel.app/community" target="_blank" class="text-emerald-300 hover:underline">Learn More →</a>
216
+ </div>
217
+ </div>
218
+ </section>
219
+ <!-- Testimonials -->
220
+ <section class="my-12">
221
+ <h2 class="text-3xl font-bold text-center mb-8">What Our Users Say</h2>
222
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
223
+ <div class="glass p-6">
224
+ <p class="mb-4">"MGZon Chatbot transformed how I code. The AI-generated snippets are accurate and save hours!"</p>
225
+ <p class="font-semibold">– Ahmed, Developer</p>
226
+ </div>
227
+ <div class="glass p-6">
228
+ <p class="mb-4">"The e-commerce tools helped me optimize my store and boost sales by 30%."</p>
229
+ <p class="font-semibold">– Sarah, Store Owner</p>
230
+ </div>
231
+ </div>
232
+ </section>
233
+ </section>
234
+ </main>
235
+ <!-- Footer -->
236
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8">
237
+ <div class="container max-w-6xl mx-auto text-center">
238
+ <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">
239
+ <p class="mb-4">
240
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
241
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
242
+ </p>
243
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
244
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
245
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
246
+ <h4 class="font-semibold mb-1">Email Us</h4>
247
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
248
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
249
+ <p>Reach out to our support team for any inquiries or assistance.</p>
250
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
251
+ </div>
252
+ </div>
253
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
254
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
255
+ <h4 class="font-semibold mb-1">Phone Support</h4>
256
+ <p>+1-800-123-4567</p>
257
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
258
+ <p>Contact our support team via phone for immediate assistance.</p>
259
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
260
+ </div>
261
+ </div>
262
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
263
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
264
+ <h4 class="font-semibold mb-1">Community</h4>
265
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
266
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
267
+ <p>Join our vibrant community to share ideas and collaborate.</p>
268
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
269
+ </div>
270
+ </div>
271
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
272
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
273
+ <h4 class="font-semibold mb-1">API Docs</h4>
274
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
275
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
276
+ <p>Explore our API documentation for seamless integration.</p>
277
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
278
+ </div>
279
+ </div>
280
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
281
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
282
+ <h4 class="font-semibold mb-1">FAQ</h4>
283
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
284
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
285
+ <p>Find answers to common questions in our FAQ section.</p>
286
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
287
+ </div>
288
+ </div>
289
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
290
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
291
+ <h4 class="font-semibold mb-1">Documentation</h4>
292
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
293
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
294
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
295
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ <div class="flex justify-center gap-6 mt-6">
300
+ <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>
301
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
302
+ <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>
303
+ </div>
304
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
305
+ </div>
306
+ </footer>
307
+ <script>
308
+ const sidebar = document.getElementById('sidebar');
309
+ const toggleBtn = document.getElementById('sidebarToggle');
310
+ const closeSidebarBtn = document.getElementById('closeSidebar');
311
+ toggleBtn.addEventListener('click', () => {
312
+ sidebar.classList.toggle('active');
313
+ sidebar.classList.toggle('-translate-x-full');
314
+ sidebar.classList.toggle('translate-x-0');
315
+ });
316
+ closeSidebarBtn.addEventListener('click', () => {
317
+ sidebar.classList.remove('active');
318
+ sidebar.classList.add('-translate-x-full');
319
+ sidebar.classList.remove('translate-x-0');
320
+ });
321
+ document.addEventListener('click', (e) => {
322
+ if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && window.innerWidth < 768) {
323
+ sidebar.classList.remove('active');
324
+ sidebar.classList.add('-translate-x-full');
325
+ sidebar.classList.remove('translate-x-0');
326
+ }
327
+ });
328
+ function showCardDetails(cardId) {
329
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
330
+ }
331
+ function closeCardDetails(cardId) {
332
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
333
+ }
334
+ const launchBtn = document.getElementById('launchBtn');
335
+ const spinner = document.getElementById('spinner');
336
+ launchBtn.addEventListener('click', (e) => {
337
+ spinner.classList.remove('hidden');
338
+ setTimeout(() => spinner.classList.add('hidden'), 2000);
339
+ });
340
+ </script>
341
+ </body>
342
+ </html>
static/images/ic/login.html ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools. Sign in with email, Google, or GitHub.">
7
+ <meta name="keywords" content="MGZon Chatbot, login, AI assistant, code generation, e-commerce, Mark Al-Asfar">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>Login - MGZon Chatbot</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="Login - MGZon Chatbot">
15
+ <meta property="og:description" content="Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/login">
18
+ <meta property="og:type" content="website">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="Login - MGZon Chatbot">
22
+ <meta name="twitter:description" content="Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "WebPage",
29
+ "name": "Login - MGZon Chatbot",
30
+ "url": "https://mgzon-mgzon-app.hf.space/login",
31
+ "description": "Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools. Sign in with email, Google, or GitHub.",
32
+ "keywords": ["MGZon Chatbot", "login", "AI chatbot", "code generation", "e-commerce", "Mark Al-Asfar", "MGZon", "MGZon AI", "E-commerce chatbot", "Python AI chatbot", "FastAPI integration"],
33
+ "isPartOf": {
34
+ "@type": "WebSite",
35
+ "name": "MGZon Chatbot",
36
+ "url": "https://mgzon-mgzon-app.hf.space/"
37
+ }
38
+ }
39
+ </script>
40
+ <!-- Tailwind (v3) -->
41
+ <script src="https://cdn.tailwindcss.com"></script>
42
+ <!-- Boxicons -->
43
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
44
+ <style>
45
+ @keyframes gradientShift {
46
+ 0% { background-position: 0% 50%; }
47
+ 50% { background-position: 100% 50%; }
48
+ 100% { background-position: 0% 50%; }
49
+ }
50
+ body {
51
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
52
+ background-size: 400% 400%;
53
+ animation: gradientShift 15s ease infinite;
54
+ font-family: system-ui, sans-serif;
55
+ }
56
+ .glass {
57
+ background: rgba(255, 255, 255, 0.07);
58
+ border-radius: 1rem;
59
+ border: 1px solid rgba(255, 255, 255, 0.12);
60
+ backdrop-filter: blur(12px);
61
+ -webkit-backdrop-filter: blur(12px);
62
+ }
63
+ .loading {
64
+ display: inline-block;
65
+ width: 1rem;
66
+ height: 1rem;
67
+ border: 2px solid currentColor;
68
+ border-top-color: transparent;
69
+ border-radius: 50%;
70
+ animation: spin 0.8s linear infinite;
71
+ margin-left: 0.5rem;
72
+ }
73
+ @keyframes spin {
74
+ to { transform: rotate(360deg); }
75
+ }
76
+ .glass:hover {
77
+ transform: scale(1.05);
78
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
79
+ background: rgba(255, 255, 255, 0.15);
80
+ }
81
+ </style>
82
+ </head>
83
+ <body class="text-white min-h-screen flex flex-col justify-center items-center">
84
+ <div class="container max-w-md mx-auto text-center py-12">
85
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-32 h-32 mx-auto mb-6 animate-bounce">
86
+ <h1 class="text-4xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">
87
+ Login to MGZon Chatbot
88
+ </h1>
89
+ <div class="glass p-8">
90
+ <form id="loginForm" action="/auth/jwt/login" method="POST" class="flex flex-col gap-4">
91
+ <input type="email" name="username" id="email" placeholder="Email" class="p-3 rounded-lg bg-gray-800/60 text-white border border-gray-700 focus:outline-none focus:ring-2 focus:ring-emerald-500" required>
92
+ <input type="password" name="password" id="password" placeholder="Password" class="p-3 rounded-lg bg-gray-800/60 text-white border border-gray-700 focus:outline-none focus:ring-2 focus:ring-emerald-500" required>
93
+ <button type="submit" id="loginBtn" class="bg-gradient-to-r from-emerald-500 to-teal-600 text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
94
+ Login <i class="bx bx-log-in ml-2"></i>
95
+ <span id="spinner" class="loading hidden"></span>
96
+ </button>
97
+ </form>
98
+ <div class="flex justify-center gap-4 mt-4 flex-wrap">
99
+ <a href="/auth/google/authorize" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
100
+ Login with Google <i class="bx bxl-google ml-2"></i>
101
+ </a>
102
+ <a href="/auth/github/authorize" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
103
+ Login with GitHub <i class="bx bxl-github ml-2"></i>
104
+ </a>
105
+ </div>
106
+ <p class="mt-4">Don't have an account? <a href="/register" class="text-emerald-300 hover:underline">Register</a></p>
107
+ <p id="errorMsg" class="text-red-500 mt-4 hidden"></p>
108
+ </div>
109
+ </div>
110
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8 w-full">
111
+ <div class="container max-w-6xl mx-auto text-center">
112
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
113
+ <p class="mb-4">
114
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
115
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
116
+ </p>
117
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
118
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
119
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
120
+ <h4 class="font-semibold mb-1">Email Us</h4>
121
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
122
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
123
+ <p>Reach out to our support team for any inquiries or assistance.</p>
124
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
125
+ </div>
126
+ </div>
127
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
128
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
129
+ <h4 class="font-semibold mb-1">Phone Support</h4>
130
+ <p>+1-800-123-4567</p>
131
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
132
+ <p>Contact our support team via phone for immediate assistance.</p>
133
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
134
+ </div>
135
+ </div>
136
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
137
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
138
+ <h4 class="font-semibold mb-1">Community</h4>
139
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
140
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
141
+ <p>Join our vibrant community to share ideas and collaborate.</p>
142
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
143
+ </div>
144
+ </div>
145
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
146
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
147
+ <h4 class="font-semibold mb-1">API Docs</h4>
148
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
149
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
150
+ <p>Explore our API documentation for seamless integration.</p>
151
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
152
+ </div>
153
+ </div>
154
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
155
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
156
+ <h4 class="font-semibold mb-1">FAQ</h4>
157
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
158
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
159
+ <p>Find answers to common questions in our FAQ section.</p>
160
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
161
+ </div>
162
+ </div>
163
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
164
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
165
+ <h4 class="font-semibold mb-1">Documentation</h4>
166
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
167
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
168
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
169
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ <div class="flex justify-center gap-6 mt-6">
174
+ <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>
175
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
176
+ <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>
177
+ </div>
178
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
179
+ </div>
180
+ </footer>
181
+ <script>
182
+ const loginForm = document.getElementById('loginForm');
183
+ const loginBtn = document.getElementById('loginBtn');
184
+ const spinner = document.getElementById('spinner');
185
+ const errorMsg = document.getElementById('errorMsg');
186
+ loginForm.addEventListener('submit', async (e) => {
187
+ e.preventDefault();
188
+ spinner.classList.remove('hidden');
189
+ errorMsg.classList.add('hidden');
190
+ const formData = new FormData(loginForm);
191
+ try {
192
+ const response = await fetch('/auth/jwt/login', {
193
+ method: 'POST',
194
+ body: formData
195
+ });
196
+ spinner.classList.add('hidden');
197
+ if (response.ok) {
198
+ window.location.href = '/chat';
199
+ } else {
200
+ const error = await response.json();
201
+ errorMsg.textContent = error.detail || 'Login failed. Please try again.';
202
+ errorMsg.classList.remove('hidden');
203
+ }
204
+ } catch (error) {
205
+ spinner.classList.add('hidden');
206
+ errorMsg.textContent = 'An error occurred. Please try again.';
207
+ errorMsg.classList.remove('hidden');
208
+ }
209
+ });
210
+ function showCardDetails(cardId) {
211
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
212
+ }
213
+ function closeCardDetails(cardId) {
214
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
215
+ }
216
+ </script>
217
+ </body>
218
+ </html>
static/images/ic/mg-128.png ADDED
static/images/ic/mg-192.png ADDED
static/images/ic/mg-256.png ADDED
static/images/ic/mg-384.png ADDED
static/images/ic/mg-48.png ADDED
static/images/ic/mg-512.png ADDED
static/images/ic/mg-72.png ADDED
static/images/ic/mg-96.png ADDED
static/images/ic/mg.svg ADDED
static/images/ic/register.html ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Register for MGZon Chatbot to access AI-powered code generation and e-commerce tools. Sign up with email, Google, or GitHub.">
7
+ <meta name="keywords" content="MGZon Chatbot, register, sign up, AI assistant, code generation, e-commerce, Mark Al-Asfar">
8
+ <meta name="author" content="Mark Al-Asfar">
9
+ <meta name="robots" content="index, follow">
10
+ <title>Register - MGZon Chatbot</title>
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
+ <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
+ <!-- Open Graph -->
14
+ <meta property="og:title" content="Register - MGZon Chatbot">
15
+ <meta property="og:description" content="Register for MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
16
+ <meta property="og:image" content="/static/images/mg.svg">
17
+ <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/register">
18
+ <meta property="og:type" content="website">
19
+ <!-- Twitter Card -->
20
+ <meta name="twitter:card" content="summary_large_image">
21
+ <meta name="twitter:title" content="Register - MGZon Chatbot">
22
+ <meta name="twitter:description" content="Register for MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
23
+ <meta name="twitter:image" content="/static/images/mg.svg">
24
+ <!-- JSON-LD -->
25
+ <script type="application/ld+json">
26
+ {
27
+ "@context": "https://schema.org",
28
+ "@type": "WebPage",
29
+ "name": "Register - MGZon Chatbot",
30
+ "url": "https://mgzon-mgzon-app.hf.space/register",
31
+ "description": "Register for MGZon Chatbot to access AI-powered code generation and e-commerce tools. Sign up with email, Google, or GitHub.",
32
+ "keywords": ["MGZon Chatbot", "register", "sign up", "AI chatbot", "code generation", "e-commerce", "Mark Al-Asfar", "MGZon", "MGZon AI", "E-commerce chatbot", "Python AI chatbot", "FastAPI integration"],
33
+ "isPartOf": {
34
+ "@type": "WebSite",
35
+ "name": "MGZon Chatbot",
36
+ "url": "https://mgzon-mgzon-app.hf.space/"
37
+ }
38
+ }
39
+ </script>
40
+ @keyframes gradientShift {
41
+ 0% { background-position: 0% 50%; }
42
+ 50% { background-position: 100% 50%; }
43
+ 100% { background-position: 0% 50%; }
44
+ }
45
+ body {
46
+ background: linear-gradient(135deg, #0f172a, #0e7490, #065f46, #064e3b);
47
+ background-size: 400% 400%;
48
+ animation: gradientShift 15s ease infinite;
49
+ font-family: system-ui, sans-serif;
50
+ }
51
+ .glass {
52
+ background: rgba(255, 255, 255, 0.07);
53
+ border-radius: 1rem;
54
+ border: 1px solid rgba(255, 255, 255, 0.12);
55
+ backdrop-filter: blur(12px);
56
+ -webkit-backdrop-filter: blur(12px);
57
+ }
58
+ .loading {
59
+ display: inline-block;
60
+ width: 1rem;
61
+ height: 1rem;
62
+ border: 2px solid currentColor;
63
+ border-top-color: transparent;
64
+ border-radius: 50%;
65
+ animation: spin 0.8s linear infinite;
66
+ margin-left: 0.5rem;
67
+ }
68
+ @keyframes spin {
69
+ to { transform: rotate(360deg); }
70
+ }
71
+ .glass:hover {
72
+ transform: scale(1.05);
73
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
74
+ background: rgba(255, 255, 255, 0.15);
75
+ }
76
+ </style>
77
+ </head>
78
+ <body class="text-white min-h-screen flex flex-col justify-center items-center">
79
+ <div class="container max-w-md mx-auto text-center py-12">
80
+ <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">
81
+ <h1 class="text-4xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-emerald-400">
82
+ Register for MGZon Chatbot
83
+ </h1>
84
+ <div class="glass p-8">
85
+ <form id="registerForm" action="/auth/register" method="POST" class="flex flex-col gap-4">
86
+ <input type="text" name="username" id="username" placeholder="Username" class="p-3 rounded-lg bg-gray-800/60 text-white border border-gray-700 focus:outline-none focus:ring-2 focus:ring-emerald-500" required>
87
+ <input type="email" name="email" id="email" placeholder="Email" class="p-3 rounded-lg bg-gray-800/60 text-white border border-gray-700 focus:outline-none focus:ring-2 focus:ring-emerald-500" required>
88
+ <input type="password" name="password" id="password" placeholder="Password" class="p-3 rounded-lg bg-gray-800/60 text-white border border-gray-700 focus:outline-none focus:ring-2 focus:ring-emerald-500" required>
89
+ <button type="submit" id="registerBtn" class="bg-gradient-to-r from-emerald-500 to-teal-600 text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
90
+ Register <i class="bx bx-user-plus ml-2"></i>
91
+ <span id="spinner" class="loading hidden"></span>
92
+ </button>
93
+ </form>
94
+ <div class="flex justify-center gap-4 mt-4 flex-wrap">
95
+ <a href="/auth/google/authorize" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
96
+ Sign Up with Google <i class="bx bxl-google ml-2"></i>
97
+ </a>
98
+ <a href="/auth/github/authorize" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
99
+ Sign Up with GitHub <i class="bx bxl-github ml-2"></i>
100
+ </a>
101
+ </div>
102
+ <p class="mt-4">Already have an account? <a href="/login" class="text-emerald-300 hover:underline">Login</a></p>
103
+ <p id="errorMsg" class="text-red-500 mt-4 hidden"></p>
104
+ </div>
105
+ </div>
106
+ <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 py-12 mt-8 w-full">
107
+ <div class="container max-w-6xl mx-auto text-center">
108
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-24 h-24 mx-auto mb-6 animate-pulse">
109
+ <p class="mb-4">
110
+ Developed by <a href="https://mark-elasfar.web.app/" target="_blank" class="text-emerald-300 hover:underline">Mark Al-Asfar</a>
111
+ | Powered by <a href="https://hager-zon.vercel.app/" target="_blank" class="text-emerald-300 hover:underline">MGZon AI</a>
112
+ </p>
113
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
114
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('email')">
115
+ <i class="bx bx-mail-send text-3xl text-emerald-300 mb-2"></i>
116
+ <h4 class="font-semibold mb-1">Email Us</h4>
117
+ <p><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
118
+ <div id="email-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
119
+ <p>Reach out to our support team for any inquiries or assistance.</p>
120
+ <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
121
+ </div>
122
+ </div>
123
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('phone')">
124
+ <i class="bx bx-phone text-3xl text-emerald-300 mb-2"></i>
125
+ <h4 class="font-semibold mb-1">Phone Support</h4>
126
+ <p>+1-800-123-4567</p>
127
+ <div id="phone-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
128
+ <p>Contact our support team via phone for immediate assistance.</p>
129
+ <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
130
+ </div>
131
+ </div>
132
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('community')">
133
+ <i class="bx bx-group text-3xl text-emerald-300 mb-2"></i>
134
+ <h4 class="font-semibold mb-1">Community</h4>
135
+ <p><a href="https://hager-zon.vercel.app/community" class="text-emerald-300 hover:underline">Join us</a></p>
136
+ <div id="community-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
137
+ <p>Join our vibrant community to share ideas and collaborate.</p>
138
+ <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
139
+ </div>
140
+ </div>
141
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('api-docs')">
142
+ <i class="bx bx-code-alt text-3xl text-emerald-300 mb-2"></i>
143
+ <h4 class="font-semibold mb-1">API Docs</h4>
144
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Explore Docs</a></p>
145
+ <div id="api-docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
146
+ <p>Explore our API documentation for seamless integration.</p>
147
+ <button onclick="closeCardDetails('api-docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
148
+ </div>
149
+ </div>
150
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('faq')">
151
+ <i class="bx bx-help-circle text-3xl text-emerald-300 mb-2"></i>
152
+ <h4 class="font-semibold mb-1">FAQ</h4>
153
+ <p><a href="https://hager-zon.vercel.app/faq" target="_blank" class="text-emerald-300 hover:underline">Read FAQ</a></p>
154
+ <div id="faq-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
155
+ <p>Find answers to common questions in our FAQ section.</p>
156
+ <button onclick="closeCardDetails('faq')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
157
+ </div>
158
+ </div>
159
+ <div class="glass p-4 cursor-pointer" onclick="showCardDetails('docs')">
160
+ <i class="bx bx-book text-3xl text-emerald-300 mb-2"></i>
161
+ <h4 class="font-semibold mb-1">Documentation</h4>
162
+ <p><a href="/docs" class="text-emerald-300 hover:underline">Full Docs</a></p>
163
+ <div id="docs-details" class="hidden mt-4 p-4 bg-gray-700/80 rounded-lg">
164
+ <p>Access comprehensive documentation for MGZon Chatbot.</p>
165
+ <button onclick="closeCardDetails('docs')" class="bg-emerald-500 text-white px-4 py-2 rounded-lg mt-2">Close</button>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ <div class="flex justify-center gap-6 mt-6">
170
+ <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>
171
+ <a href="https://x.com/MGZon" class="text-2xl text-white hover:text-emerald-300 transition"><i class="bx bxl-twitter"></i></a>
172
+ <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>
173
+ </div>
174
+ <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
175
+ </div>
176
+ </footer>
177
+ <script>
178
+ const registerForm = document.getElementById('registerForm');
179
+ const registerBtn = document.getElementById('registerBtn');
180
+ const spinner = document.getElementById('spinner');
181
+ const errorMsg = document.getElementById('errorMsg');
182
+ registerForm.addEventListener('submit', async (e) => {
183
+ e.preventDefault();
184
+ spinner.classList.remove('hidden');
185
+ errorMsg.classList.add('hidden');
186
+ const formData = new FormData(registerForm);
187
+ try {
188
+ const response = await fetch('/auth/register', {
189
+ method: 'POST',
190
+ body: formData
191
+ });
192
+ spinner.classList.add('hidden');
193
+ if (response.ok) {
194
+ window.location.href = '/chat';
195
+ } else {
196
+ const error = await response.json();
197
+ errorMsg.textContent = error.detail || 'Registration failed. Please try again.';
198
+ errorMsg.classList.remove('hidden');
199
+ }
200
+ } catch (error) {
201
+ spinner.classList.add('hidden');
202
+ errorMsg.textContent = 'An error occurred. Please try again.';
203
+ errorMsg.classList.remove('hidden');
204
+ }
205
+ });
206
+ function showCardDetails(cardId) {
207
+ document.getElementById(`${cardId}-details`).classList.remove('hidden');
208
+ }
209
+ function closeCardDetails(cardId) {
210
+ document.getElementById(`${cardId}-details`).classList.add('hidden');
211
+ }
212
+ </script>
213
+ </body>
214
+ </html>
static/images/icons/mg-128.png ADDED
static/images/icons/mg-192.png ADDED
static/images/icons/mg-256.png ADDED
static/images/icons/mg-384.png ADDED
static/images/icons/mg-48.png ADDED
static/images/icons/mg-512.png ADDED
static/images/icons/mg-72.png ADDED
static/images/icons/mg-96.png ADDED
static/images/mg.png ADDED
static/images/mg.svg CHANGED
static/images/mgz.svg ADDED
static/images/swipe-hint.svg ADDED
static/js/chat.js CHANGED
@@ -1,33 +1,42 @@
1
- // static/js/chat.js
2
  // SPDX-FileCopyrightText: Hadad <[email protected]>
3
  // SPDX-License-Identifier: Apache-2.0
4
 
5
- // Prism.
6
  Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/';
7
 
8
- // UI elements.
9
- const chatArea = document.getElementById('chatArea');
10
- const chatBox = document.getElementById('chatBox');
11
- const initialContent = document.getElementById('initialContent');
12
- const form = document.getElementById('footerForm');
13
- const input = document.getElementById('userInput');
14
- 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');
21
- const audioPreview = document.getElementById('audioPreview');
22
- const promptItems = document.querySelectorAll('.prompt-item');
23
- const mainHeader = document.getElementById('mainHeader');
24
- const chatHeader = document.getElementById('chatHeader');
25
- const homeBtn = document.getElementById('homeBtn');
26
- const clearBtn = document.getElementById('clearBtn');
27
- const loginBtn = document.getElementById('loginBtn');
28
- const messageLimitWarning = document.getElementById('messageLimitWarning');
29
-
30
- // Track state.
 
 
 
 
 
 
 
 
 
 
31
  let streamMsg = null;
32
  let conversationHistory = JSON.parse(sessionStorage.getItem('conversationHistory') || '[]');
33
  let currentAssistantText = '';
@@ -36,43 +45,60 @@ let abortController = null;
36
  let mediaRecorder = null;
37
  let audioChunks = [];
38
  let isRecording = false;
 
 
 
39
 
40
  // Auto-resize textarea
41
  function autoResizeTextarea() {
42
- if (input) {
43
- input.style.height = 'auto';
44
- input.style.height = `${Math.min(input.scrollHeight, 200)}px`; // Max height is 200px as per CSS
45
  }
46
  }
47
 
48
- // تحميل المحادثة عند تحميل الصفحة
49
- document.addEventListener('DOMContentLoaded', () => {
50
  AOS.init({
51
  duration: 800,
52
  easing: 'ease-out-cubic',
53
  once: true,
54
  offset: 50,
55
  });
56
- if (conversationHistory.length > 0) {
 
 
57
  enterChatView();
58
- conversationHistory.forEach(msg => {
59
- addMsg(msg.role, msg.content);
60
- });
 
61
  }
62
- // Initialize textarea height
63
  autoResizeTextarea();
64
- // Enable send button based on input
65
- if (btn && input && fileInput && audioInput) {
66
- btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
 
 
67
  }
 
68
  });
69
 
70
- // تحقق من الـ token
71
  function checkAuth() {
72
  return localStorage.getItem('token');
73
  }
74
 
75
- // Render markdown content.
 
 
 
 
 
 
 
 
 
76
  function renderMarkdown(el) {
77
  const raw = el.dataset.text || '';
78
  const html = marked.parse(raw, {
@@ -82,201 +108,149 @@ function renderMarkdown(el) {
82
  smartypants: false,
83
  headerIds: false,
84
  });
85
- el.innerHTML = '<div class="md-content">' + html + '</div>';
86
  const wrapper = el.querySelector('.md-content');
87
-
88
- // Wrap tables.
89
- const tables = wrapper.querySelectorAll('table');
90
- tables.forEach(t => {
91
- if (t.parentNode && t.parentNode.classList && t.parentNode.classList.contains('table-wrapper')) return;
92
- const div = document.createElement('div');
93
- div.className = 'table-wrapper';
94
- t.parentNode.insertBefore(div, t);
95
- div.appendChild(t);
96
- });
97
-
98
- // Style horizontal rules.
99
- const hrs = wrapper.querySelectorAll('hr');
100
- hrs.forEach(h => {
101
- if (!h.classList.contains('styled-hr')) {
102
- h.classList.add('styled-hr');
103
  }
104
  });
105
-
106
- // Highlight code.
107
  Prism.highlightAllUnder(wrapper);
108
  }
109
 
110
- // Chat view.
111
  function enterChatView() {
112
- if (mainHeader) {
113
- mainHeader.style.display = 'none';
114
- }
115
- if (chatHeader) {
116
- chatHeader.style.display = 'flex';
117
- chatHeader.setAttribute('aria-hidden', 'false');
118
- }
119
- if (chatBox) {
120
- chatBox.style.display = 'flex';
121
- }
122
- if (initialContent) {
123
- initialContent.style.display = 'none';
124
  }
 
 
125
  }
126
 
127
- // Home view.
128
  function leaveChatView() {
129
- if (mainHeader) {
130
- mainHeader.style.display = 'flex';
131
- }
132
- if (chatHeader) {
133
- chatHeader.style.display = 'none';
134
- chatHeader.setAttribute('aria-hidden', 'true');
135
- }
136
- if (chatBox) {
137
- chatBox.style.display = 'none';
138
- }
139
- if (initialContent) {
140
- initialContent.style.display = 'flex';
141
  }
 
 
142
  }
143
 
144
- // Chat bubble.
145
  function addMsg(who, text) {
146
  const div = document.createElement('div');
147
- div.className = 'bubble ' + (who === 'user' ? 'bubble-user' : 'bubble-assist');
148
  div.dataset.text = text;
149
  renderMarkdown(div);
150
- if (chatBox) {
151
- chatBox.appendChild(div);
152
- chatBox.style.display = 'flex';
153
- chatBox.scrollTop = chatBox.scrollHeight;
154
  }
155
  return div;
156
  }
157
 
158
- // Clear all chat.
159
  function clearAllMessages() {
160
  stopStream(true);
161
  conversationHistory = [];
162
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
163
  currentAssistantText = '';
164
  if (streamMsg) {
165
- const loadingEl = streamMsg.querySelector('.loading');
166
- if (loadingEl) loadingEl.remove();
167
  streamMsg = null;
168
  }
169
- if (chatBox) {
170
- chatBox.innerHTML = '';
171
- }
172
- if (input) {
173
- input.value = '';
174
- }
175
- if (btn) {
176
- btn.disabled = true;
177
- }
178
- if (stopBtn) {
179
- stopBtn.style.display = 'none';
180
- }
181
- if (btn) {
182
- btn.style.display = 'inline-flex';
183
- }
184
- if (filePreview) {
185
- filePreview.style.display = 'none';
186
- }
187
- if (audioPreview) {
188
- audioPreview.style.display = 'none';
189
- }
190
- if (messageLimitWarning) {
191
- messageLimitWarning.classList.add('hidden');
192
- }
193
  enterChatView();
194
  autoResizeTextarea();
195
  }
196
 
197
- // File preview.
198
  function previewFile() {
199
- if (fileInput && fileInput.files.length > 0) {
200
- const file = fileInput.files[0];
201
  if (file.type.startsWith('image/')) {
202
  const reader = new FileReader();
203
  reader.onload = e => {
204
- if (filePreview) {
205
- filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
206
- filePreview.style.display = 'block';
207
- }
208
- if (audioPreview) {
209
- audioPreview.style.display = 'none';
210
- }
211
- if (btn) {
212
- btn.disabled = false; // Enable send button when file is selected
213
  }
 
 
214
  };
215
  reader.readAsDataURL(file);
216
  }
217
  }
218
- if (audioInput && audioInput.files.length > 0) {
219
- const file = audioInput.files[0];
220
  if (file.type.startsWith('audio/')) {
221
  const reader = new FileReader();
222
  reader.onload = e => {
223
- if (audioPreview) {
224
- audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
225
- audioPreview.style.display = 'block';
226
- }
227
- if (filePreview) {
228
- filePreview.style.display = 'none';
229
- }
230
- if (btn) {
231
- btn.disabled = false; // Enable send button when audio is selected
232
  }
 
 
233
  };
234
  reader.readAsDataURL(file);
235
  }
236
  }
237
  }
238
 
239
- // Voice recording.
240
  function startVoiceRecording() {
241
  if (isRequestActive || isRecording) return;
242
  isRecording = true;
243
- if (btn) {
244
- btn.classList.add('recording');
245
- }
246
  navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
247
  mediaRecorder = new MediaRecorder(stream);
248
  audioChunks = [];
249
  mediaRecorder.start();
250
- mediaRecorder.addEventListener('dataavailable', event => {
251
- audioChunks.push(event.data);
252
- });
253
  }).catch(err => {
254
  console.error('Error accessing microphone:', err);
255
  alert('Failed to access microphone. Please check permissions.');
256
  isRecording = false;
257
- if (btn) {
258
- btn.classList.remove('recording');
259
- }
260
  });
261
  }
262
 
263
  function stopVoiceRecording() {
264
- if (mediaRecorder && mediaRecorder.state === 'recording') {
265
  mediaRecorder.stop();
266
- if (btn) {
267
- btn.classList.remove('recording');
268
- }
269
  isRecording = false;
270
  mediaRecorder.addEventListener('stop', async () => {
271
  const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
272
  const formData = new FormData();
273
  formData.append('file', audioBlob, 'voice-message.webm');
274
- submitAudioMessage(formData);
275
  });
276
  }
277
  }
278
 
279
- // Send audio message.
280
  async function submitAudioMessage(formData) {
281
  enterChatView();
282
  addMsg('user', 'Voice message');
@@ -286,144 +260,370 @@ async function submitAudioMessage(formData) {
286
  const loadingEl = document.createElement('span');
287
  loadingEl.className = 'loading';
288
  streamMsg.appendChild(loadingEl);
289
- if (stopBtn) {
290
- stopBtn.style.display = 'inline-flex';
291
- }
292
- if (btn) {
293
- btn.style.display = 'none';
294
- }
295
- if (input) {
296
- input.value = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  }
298
- if (btn) {
299
- btn.disabled = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  }
301
- if (filePreview) {
302
- filePreview.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
- if (audioPreview) {
305
- audioPreview.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
 
307
 
308
- isRequestActive = true;
309
- abortController = new AbortController();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
 
 
311
  try {
312
- const token = checkAuth();
313
- const headers = token ? { Authorization: `Bearer ${token}` } : {};
314
- const response = await fetch('/api/audio-transcription', {
315
  method: 'POST',
316
- body: formData,
317
- headers: headers,
318
- signal: abortController.signal,
 
 
319
  });
 
 
 
 
 
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  if (!response.ok) {
322
- if (response.status === 403) {
323
- if (messageLimitWarning) {
324
- messageLimitWarning.classList.remove('hidden');
325
- }
326
- if (input) {
327
- input.disabled = true;
328
- }
329
- if (streamMsg) {
330
- const loadingEl = streamMsg.querySelector('.loading');
331
- if (loadingEl) loadingEl.remove();
332
- streamMsg = null;
333
- }
334
- isRequestActive = false;
335
- abortController = null;
336
- if (btn) {
337
- btn.style.display = 'inline-flex';
338
- }
339
- if (stopBtn) {
340
- stopBtn.style.display = 'none';
341
- }
342
- setTimeout(() => {
343
- window.location.href = '/login';
344
- }, 3000); // تأخير 3 ثواني قبل التحويل
345
- return;
346
- }
347
  if (response.status === 401) {
348
  localStorage.removeItem('token');
349
  window.location.href = '/login';
350
- return;
351
  }
352
- throw new Error('Request failed');
353
  }
354
-
355
  const data = await response.json();
356
- const transcription = data.transcription || 'Error: No transcription generated.';
357
- if (streamMsg) {
358
- streamMsg.dataset.text = transcription;
359
- renderMarkdown(streamMsg);
360
- streamMsg.dataset.done = '1';
361
- }
362
- conversationHistory.push({ role: 'assistant', content: transcription });
363
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
 
 
 
 
 
 
 
 
 
 
 
364
 
365
- streamMsg = null;
366
- isRequestActive = false;
367
- abortController = null;
368
- if (btn) {
369
- btn.style.display = 'inline-flex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
371
- if (stopBtn) {
372
- stopBtn.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
- } catch (error) {
375
- if (streamMsg) {
376
- const loadingEl = streamMsg.querySelector('.loading');
377
- if (loadingEl) loadingEl.remove();
378
- streamMsg.dataset.text = error.message || 'An error occurred during the request.';
379
- renderMarkdown(streamMsg);
380
- streamMsg.dataset.done = '1';
381
- streamMsg = null;
382
- isRequestActive = false;
383
- abortController = null;
384
  }
385
- if (btn) {
386
- btn.style.display = 'inline-flex';
 
 
 
 
 
 
387
  }
388
- if (stopBtn) {
389
- stopBtn.style.display = 'none';
 
 
 
 
 
 
 
390
  }
391
- }
392
  }
393
- // Send user message.
 
394
  async function submitMessage() {
395
  if (isRequestActive || isRecording) return;
396
- let message = input.value.trim();
397
- let formData = new FormData();
 
398
  let endpoint = '/api/chat';
 
399
  let inputType = 'text';
400
  let outputFormat = 'text';
 
401
 
402
- if (fileInput && fileInput.files.length > 0) {
403
- const file = fileInput.files[0];
404
  if (file.type.startsWith('image/')) {
405
  endpoint = '/api/image-analysis';
406
  inputType = 'image';
407
  message = 'Analyze this image';
 
408
  formData.append('file', file);
409
  formData.append('output_format', 'text');
410
  }
411
- } else if (audioInput && audioInput.files.length > 0) {
412
- const file = audioInput.files[0];
413
  if (file.type.startsWith('audio/')) {
414
  endpoint = '/api/audio-transcription';
415
  inputType = 'audio';
416
  message = 'Transcribe this audio';
 
417
  formData.append('file', file);
418
  }
419
  } else if (message) {
420
- formData.append('message', message);
421
- formData.append('system_prompt', 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.');
422
- formData.append('history', JSON.stringify(conversationHistory));
423
- formData.append('temperature', '0.7');
424
- formData.append('max_new_tokens', '128000');
425
- formData.append('enable_browsing', 'true');
426
- formData.append('output_format', 'text');
 
 
 
 
427
  } else {
428
  return;
429
  }
@@ -436,63 +636,24 @@ async function submitMessage() {
436
  const loadingEl = document.createElement('span');
437
  loadingEl.className = 'loading';
438
  streamMsg.appendChild(loadingEl);
439
- if (stopBtn) {
440
- stopBtn.style.display = 'inline-flex';
441
- }
442
- if (btn) {
443
- btn.style.display = 'none';
444
- }
445
- if (input) {
446
- input.value = '';
447
- }
448
- if (btn) {
449
- btn.disabled = true;
450
- }
451
- if (filePreview) {
452
- filePreview.style.display = 'none';
453
- }
454
- if (audioPreview) {
455
- audioPreview.style.display = 'none';
456
- }
457
- autoResizeTextarea();
458
 
459
  isRequestActive = true;
460
  abortController = new AbortController();
461
 
462
  try {
463
- const token = checkAuth();
464
- const headers = token ? { Authorization: `Bearer ${token}` } : {};
465
- const response = await fetch(endpoint, {
466
- method: 'POST',
467
- body: formData,
468
- headers: headers,
469
- signal: abortController.signal,
470
- });
471
-
472
  if (!response.ok) {
473
  if (response.status === 403) {
474
- if (messageLimitWarning) {
475
- messageLimitWarning.classList.remove('hidden');
476
- }
477
- if (input) {
478
- input.disabled = true;
479
- }
480
- if (streamMsg) {
481
- const loadingEl = streamMsg.querySelector('.loading');
482
- if (loadingEl) loadingEl.remove();
483
- streamMsg = null;
484
- }
485
  isRequestActive = false;
486
  abortController = null;
487
- if (btn) {
488
- btn.style.display = 'inline-flex';
489
- }
490
- if (stopBtn) {
491
- stopBtn.style.display = 'none';
492
- }
493
- setTimeout(() => {
494
- window.location.href = '/login';
495
- }, 3000); // تأخير 3 ثواني قبل التحويل
496
  return;
497
  }
498
  if (response.status === 401) {
@@ -500,11 +661,12 @@ async function submitMessage() {
500
  window.location.href = '/login';
501
  return;
502
  }
503
- throw new Error('Request failed');
504
  }
505
 
506
  if (endpoint === '/api/audio-transcription') {
507
  const data = await response.json();
 
508
  const transcription = data.transcription || 'Error: No transcription generated.';
509
  if (streamMsg) {
510
  streamMsg.dataset.text = transcription;
@@ -513,6 +675,16 @@ async function submitMessage() {
513
  }
514
  conversationHistory.push({ role: 'assistant', content: transcription });
515
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
 
 
 
 
 
 
 
 
 
 
516
  } else if (endpoint === '/api/image-analysis') {
517
  const data = await response.json();
518
  const analysis = data.image_analysis || 'Error: No analysis generated.';
@@ -523,218 +695,293 @@ async function submitMessage() {
523
  }
524
  conversationHistory.push({ role: 'assistant', content: analysis });
525
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
 
 
 
 
 
 
 
 
 
 
526
  } else {
527
- const reader = response.body.getReader();
528
- const decoder = new TextDecoder();
529
- let buffer = '';
530
-
531
- while (true) {
532
- const { done, value } = await reader.read();
533
- if (done) break;
534
- buffer += decoder.decode(value, { stream: true });
535
  if (streamMsg) {
536
- streamMsg.dataset.text = buffer;
537
- currentAssistantText = buffer;
538
- const loadingEl = streamMsg.querySelector('.loading');
539
- if (loadingEl) loadingEl.remove();
540
  renderMarkdown(streamMsg);
541
- if (chatBox) {
542
- chatBox.scrollTop = chatBox.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  }
544
  }
 
 
 
 
 
 
545
  }
546
- if (streamMsg) {
547
- streamMsg.dataset.done = '1';
548
- }
549
- conversationHistory.push({ role: 'assistant', content: buffer });
550
- sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
551
- }
552
-
553
- streamMsg = null;
554
- isRequestActive = false;
555
- abortController = null;
556
- if (btn) {
557
- btn.style.display = 'inline-flex';
558
- }
559
- if (stopBtn) {
560
- stopBtn.style.display = 'none';
561
  }
 
562
  } catch (error) {
563
- if (streamMsg) {
564
- const loadingEl = streamMsg.querySelector('.loading');
565
- if (loadingEl) loadingEl.remove();
566
- streamMsg.dataset.text = error.message || 'An error occurred during the request.';
567
- renderMarkdown(streamMsg);
568
- streamMsg.dataset.done = '1';
569
- streamMsg = null;
570
- isRequestActive = false;
571
- abortController = null;
572
- }
573
- if (btn) {
574
- btn.style.display = 'inline-flex';
575
- }
576
- if (stopBtn) {
577
- stopBtn.style.display = 'none';
578
- }
579
  }
580
  }
581
- // Stop streaming and cancel the ongoing request.
 
582
  function stopStream(forceCancel = false) {
583
  if (!isRequestActive && !isRecording) return;
584
- if (isRecording) {
585
- stopVoiceRecording();
586
- }
587
  isRequestActive = false;
588
  if (abortController) {
589
  abortController.abort();
590
  abortController = null;
591
  }
592
  if (streamMsg && !forceCancel) {
593
- const loadingEl = streamMsg.querySelector('.loading');
594
- if (loadingEl) loadingEl.remove();
595
  streamMsg.dataset.text += '';
596
  renderMarkdown(streamMsg);
597
  streamMsg.dataset.done = '1';
598
  streamMsg = null;
599
  }
600
- if (stopBtn) {
601
- stopBtn.style.display = 'none';
602
- }
603
- if (btn) {
604
- btn.style.display = 'inline-flex';
605
- }
606
- if (stopBtn) {
607
- stopBtn.style.pointerEvents = 'auto';
608
- }
609
  }
610
 
611
- // Prompts.
612
- promptItems.forEach(p => {
613
- p.addEventListener('click', e => {
614
- e.preventDefault();
615
- if (input) {
616
- input.value = p.dataset.prompt;
617
- autoResizeTextarea();
618
- }
619
- if (btn) {
620
- btn.disabled = false;
621
- }
622
- submitMessage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  });
624
- });
625
 
626
- // File and audio inputs.
627
- if (fileBtn) {
628
- fileBtn.addEventListener('click', () => {
629
- if (fileInput) {
630
- fileInput.click();
631
- }
632
  });
633
  }
634
- if (audioBtn) {
635
- audioBtn.addEventListener('click', () => {
636
- if (audioInput) {
637
- audioInput.click();
638
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  });
640
  }
641
- if (fileInput) {
642
- fileInput.addEventListener('change', previewFile);
643
- }
644
- if (audioInput) {
645
- audioInput.addEventListener('change', previewFile);
646
- }
647
 
648
- // Send button events for send and voice recording.
649
- if (btn) {
650
- btn.addEventListener('mousedown', e => {
651
- if (btn.disabled || isRequestActive || isRecording) return;
652
- startVoiceRecording();
653
- });
654
- btn.addEventListener('mouseup', e => {
655
- if (isRecording) {
656
- stopVoiceRecording();
 
 
 
657
  }
658
  });
659
- btn.addEventListener('mouseleave', e => {
660
- if (isRecording) {
661
- stopVoiceRecording();
 
 
 
 
 
 
662
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  });
664
- btn.addEventListener('touchstart', e => {
 
 
665
  e.preventDefault();
666
- if (btn.disabled || isRequestActive || isRecording) return;
667
  startVoiceRecording();
668
  });
669
- btn.addEventListener('touchend', e => {
670
  e.preventDefault();
671
- if (isRecording) {
672
- stopVoiceRecording();
673
- }
674
  });
675
- btn.addEventListener('touchcancel', e => {
676
  e.preventDefault();
677
- if (isRecording) {
678
- stopVoiceRecording();
679
- }
680
  });
681
  }
682
 
683
- // Submit.
684
- if (form) {
685
- form.addEventListener('submit', e => {
686
  e.preventDefault();
687
- if (!isRecording) {
688
- submitMessage();
689
- }
690
  });
691
  }
692
 
693
- // Handle Enter key to submit without adding new line.
694
- if (input) {
695
- input.addEventListener('keydown', e => {
696
  if (e.key === 'Enter' && !e.shiftKey) {
697
  e.preventDefault();
698
- if (!isRecording && !btn.disabled) {
699
- submitMessage();
700
- }
701
  }
702
  });
703
-
704
- // Auto-resize textarea on input.
705
- input.addEventListener('input', () => {
706
  autoResizeTextarea();
707
- if (btn && input && fileInput && audioInput) {
708
- btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
709
- }
710
  });
711
  }
712
 
713
- // Stop.
714
- if (stopBtn) {
715
- stopBtn.addEventListener('click', () => {
716
- stopBtn.style.pointerEvents = 'none';
717
  stopStream();
718
  });
719
  }
720
 
721
- // Home.
722
- if (homeBtn) {
723
- homeBtn.addEventListener('click', () => {
724
- window.location.href = '/';
 
 
 
 
 
725
  });
726
  }
727
 
728
- // Clear messages.
729
- if (clearBtn) {
730
- clearBtn.addEventListener('click', () => {
731
- clearAllMessages();
732
- });
733
  }
734
 
735
- // Login button.
736
- if (loginBtn) {
737
- loginBtn.addEventListener('click', () => {
738
- window.location.href = '/login';
739
- });
740
  }
 
 
1
  // SPDX-FileCopyrightText: Hadad <[email protected]>
2
  // SPDX-License-Identifier: Apache-2.0
3
 
4
+ // Prism for code highlighting
5
  Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/';
6
 
7
+ // UI elements
8
+ const uiElements = {
9
+ chatArea: document.getElementById('chatArea'),
10
+ chatBox: document.getElementById('chatBox'),
11
+ initialContent: document.getElementById('initialContent'),
12
+ form: document.getElementById('footerForm'),
13
+ input: document.getElementById('userInput'),
14
+ sendBtn: document.getElementById('sendBtn'),
15
+ stopBtn: document.getElementById('stopBtn'),
16
+ fileBtn: document.getElementById('fileBtn'),
17
+ audioBtn: document.getElementById('audioBtn'),
18
+ fileInput: document.getElementById('fileInput'),
19
+ audioInput: document.getElementById('audioInput'),
20
+ filePreview: document.getElementById('filePreview'),
21
+ audioPreview: document.getElementById('audioPreview'),
22
+ promptItems: document.querySelectorAll('.prompt-item'),
23
+ chatHeader: document.getElementById('chatHeader'),
24
+ clearBtn: document.getElementById('clearBtn'),
25
+ messageLimitWarning: document.getElementById('messageLimitWarning'),
26
+ conversationTitle: document.getElementById('conversationTitle'),
27
+ sidebar: document.getElementById('sidebar'),
28
+ sidebarToggle: document.getElementById('sidebarToggle'),
29
+ conversationList: document.getElementById('conversationList'),
30
+ newConversationBtn: document.getElementById('newConversationBtn'),
31
+ swipeHint: document.getElementById('swipeHint'),
32
+ settingsBtn: document.getElementById('settingsBtn'),
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 = '';
 
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
+ // Initialize page
61
+ document.addEventListener('DOMContentLoaded', async () => {
62
  AOS.init({
63
  duration: 800,
64
  easing: 'ease-out-cubic',
65
  once: true,
66
  offset: 50,
67
  });
68
+ if (currentConversationId && checkAuth()) {
69
+ await loadConversation(currentConversationId);
70
+ } else if (conversationHistory.length > 0) {
71
  enterChatView();
72
+ conversationHistory.forEach(msg => addMsg(msg.role, msg.content));
73
+ }
74
+ if (checkAuth()) {
75
+ await loadConversations();
76
  }
 
77
  autoResizeTextarea();
78
+ updateSendButtonState();
79
+ if (uiElements.swipeHint) {
80
+ setTimeout(() => {
81
+ uiElements.swipeHint.style.display = 'none';
82
+ }, 3000);
83
  }
84
+ setupTouchGestures();
85
  });
86
 
87
+ // Check authentication token
88
  function checkAuth() {
89
  return localStorage.getItem('token');
90
  }
91
 
92
+ // Update send button state
93
+ function updateSendButtonState() {
94
+ if (uiElements.sendBtn && uiElements.input && uiElements.fileInput && uiElements.audioInput) {
95
+ uiElements.sendBtn.disabled = uiElements.input.value.trim() === '' &&
96
+ uiElements.fileInput.files.length === 0 &&
97
+ uiElements.audioInput.files.length === 0;
98
+ }
99
+ }
100
+
101
+ // Render markdown content
102
  function renderMarkdown(el) {
103
  const raw = el.dataset.text || '';
104
  const html = marked.parse(raw, {
 
108
  smartypants: false,
109
  headerIds: false,
110
  });
111
+ el.innerHTML = `<div class="md-content">${html}</div>`;
112
  const wrapper = el.querySelector('.md-content');
113
+ wrapper.querySelectorAll('table').forEach(t => {
114
+ if (!t.parentNode.classList?.contains('table-wrapper')) {
115
+ const div = document.createElement('div');
116
+ div.className = 'table-wrapper';
117
+ t.parentNode.insertBefore(div, t);
118
+ div.appendChild(t);
 
 
 
 
 
 
 
 
 
 
119
  }
120
  });
121
+ wrapper.querySelectorAll('hr').forEach(h => h.classList.add('styled-hr'));
 
122
  Prism.highlightAllUnder(wrapper);
123
  }
124
 
125
+ // Toggle chat view
126
  function enterChatView() {
127
+ if (uiElements.chatHeader) {
128
+ uiElements.chatHeader.classList.remove('hidden');
129
+ uiElements.chatHeader.setAttribute('aria-hidden', 'false');
130
+ if (currentConversationTitle && uiElements.conversationTitle) {
131
+ uiElements.conversationTitle.textContent = currentConversationTitle;
132
+ }
 
 
 
 
 
 
133
  }
134
+ if (uiElements.chatBox) uiElements.chatBox.classList.remove('hidden');
135
+ if (uiElements.initialContent) uiElements.initialContent.classList.add('hidden');
136
  }
137
 
138
+ // Toggle home view
139
  function leaveChatView() {
140
+ if (uiElements.chatHeader) {
141
+ uiElements.chatHeader.classList.add('hidden');
142
+ uiElements.chatHeader.setAttribute('aria-hidden', 'true');
 
 
 
 
 
 
 
 
 
143
  }
144
+ if (uiElements.chatBox) uiElements.chatBox.classList.add('hidden');
145
+ if (uiElements.initialContent) uiElements.initialContent.classList.remove('hidden');
146
  }
147
 
148
+ // Add chat bubble
149
  function addMsg(who, text) {
150
  const div = document.createElement('div');
151
+ div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'}`;
152
  div.dataset.text = text;
153
  renderMarkdown(div);
154
+ if (uiElements.chatBox) {
155
+ uiElements.chatBox.appendChild(div);
156
+ uiElements.chatBox.classList.remove('hidden');
157
+ uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
158
  }
159
  return div;
160
  }
161
 
162
+ // Clear all messages
163
  function clearAllMessages() {
164
  stopStream(true);
165
  conversationHistory = [];
166
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
167
  currentAssistantText = '';
168
  if (streamMsg) {
169
+ streamMsg.querySelector('.loading')?.remove();
 
170
  streamMsg = null;
171
  }
172
+ if (uiElements.chatBox) uiElements.chatBox.innerHTML = '';
173
+ if (uiElements.input) uiElements.input.value = '';
174
+ if (uiElements.sendBtn) uiElements.sendBtn.disabled = true;
175
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
176
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
177
+ if (uiElements.filePreview) uiElements.filePreview.style.display = 'none';
178
+ if (uiElements.audioPreview) uiElements.audioPreview.style.display = 'none';
179
+ if (uiElements.messageLimitWarning) uiElements.messageLimitWarning.classList.add('hidden');
180
+ currentConversationId = null;
181
+ currentConversationTitle = null;
182
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = 'MGZon AI Assistant';
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  enterChatView();
184
  autoResizeTextarea();
185
  }
186
 
187
+ // File preview
188
  function previewFile() {
189
+ if (uiElements.fileInput?.files.length > 0) {
190
+ const file = uiElements.fileInput.files[0];
191
  if (file.type.startsWith('image/')) {
192
  const reader = new FileReader();
193
  reader.onload = e => {
194
+ if (uiElements.filePreview) {
195
+ uiElements.filePreview.innerHTML = `<img src="${e.target.result}" class="upload-preview">`;
196
+ uiElements.filePreview.style.display = 'block';
 
 
 
 
 
 
197
  }
198
+ if (uiElements.audioPreview) uiElements.audioPreview.style.display = 'none';
199
+ updateSendButtonState();
200
  };
201
  reader.readAsDataURL(file);
202
  }
203
  }
204
+ if (uiElements.audioInput?.files.length > 0) {
205
+ const file = uiElements.audioInput.files[0];
206
  if (file.type.startsWith('audio/')) {
207
  const reader = new FileReader();
208
  reader.onload = e => {
209
+ if (uiElements.audioPreview) {
210
+ uiElements.audioPreview.innerHTML = `<audio controls src="${e.target.result}"></audio>`;
211
+ uiElements.audioPreview.style.display = 'block';
 
 
 
 
 
 
212
  }
213
+ if (uiElements.filePreview) uiElements.filePreview.style.display = 'none';
214
+ updateSendButtonState();
215
  };
216
  reader.readAsDataURL(file);
217
  }
218
  }
219
  }
220
 
221
+ // Voice recording
222
  function startVoiceRecording() {
223
  if (isRequestActive || isRecording) return;
224
  isRecording = true;
225
+ if (uiElements.sendBtn) uiElements.sendBtn.classList.add('recording');
 
 
226
  navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
227
  mediaRecorder = new MediaRecorder(stream);
228
  audioChunks = [];
229
  mediaRecorder.start();
230
+ mediaRecorder.addEventListener('dataavailable', event => audioChunks.push(event.data));
 
 
231
  }).catch(err => {
232
  console.error('Error accessing microphone:', err);
233
  alert('Failed to access microphone. Please check permissions.');
234
  isRecording = false;
235
+ if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
 
 
236
  });
237
  }
238
 
239
  function stopVoiceRecording() {
240
+ if (mediaRecorder?.state === 'recording') {
241
  mediaRecorder.stop();
242
+ if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
 
 
243
  isRecording = false;
244
  mediaRecorder.addEventListener('stop', async () => {
245
  const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
246
  const formData = new FormData();
247
  formData.append('file', audioBlob, 'voice-message.webm');
248
+ await submitAudioMessage(formData);
249
  });
250
  }
251
  }
252
 
253
+ // Send audio message
254
  async function submitAudioMessage(formData) {
255
  enterChatView();
256
  addMsg('user', 'Voice message');
 
260
  const loadingEl = document.createElement('span');
261
  loadingEl.className = 'loading';
262
  streamMsg.appendChild(loadingEl);
263
+ updateUIForRequest();
264
+
265
+ isRequestActive = true;
266
+ abortController = new AbortController();
267
+
268
+ try {
269
+ const response = await sendRequest('/api/audio-transcription', formData);
270
+ const data = await response.json();
271
+ if (!data.transcription) throw new Error('No transcription received from server');
272
+ const transcription = data.transcription || 'Error: No transcription generated.';
273
+ if (streamMsg) {
274
+ streamMsg.dataset.text = transcription;
275
+ renderMarkdown(streamMsg);
276
+ streamMsg.dataset.done = '1';
277
+ }
278
+ conversationHistory.push({ role: 'assistant', content: transcription });
279
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
280
+ if (checkAuth() && currentConversationId) {
281
+ await saveMessageToConversation(currentConversationId, 'assistant', transcription);
282
+ }
283
+ if (checkAuth() && data.conversation_id) {
284
+ currentConversationId = data.conversation_id;
285
+ currentConversationTitle = data.conversation_title || 'Untitled Conversation';
286
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
287
+ history.pushState(null, '', `/chat/${currentConversationId}`);
288
+ await loadConversations();
289
+ }
290
+ finalizeRequest();
291
+ } catch (error) {
292
+ handleRequestError(error);
293
  }
294
+ }
295
+
296
+ // Helper to send API requests
297
+ async function sendRequest(endpoint, body, headers = {}) {
298
+ const token = checkAuth();
299
+ if (token) headers['Authorization'] = `Bearer ${token}`;
300
+ return await fetch(endpoint, {
301
+ method: 'POST',
302
+ body,
303
+ headers,
304
+ signal: abortController.signal,
305
+ });
306
+ }
307
+
308
+ // Helper to update UI during request
309
+ function updateUIForRequest() {
310
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'inline-flex';
311
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'none';
312
+ if (uiElements.input) uiElements.input.value = '';
313
+ if (uiElements.sendBtn) uiElements.sendBtn.disabled = true;
314
+ if (uiElements.filePreview) uiElements.filePreview.style.display = 'none';
315
+ if (uiElements.audioPreview) uiElements.audioPreview.style.display = 'none';
316
+ autoResizeTextarea();
317
+ }
318
+
319
+ // Helper to finalize request
320
+ function finalizeRequest() {
321
+ streamMsg = null;
322
+ isRequestActive = false;
323
+ abortController = null;
324
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
325
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
326
+ }
327
+
328
+ // Helper to handle request errors
329
+ function handleRequestError(error) {
330
+ if (streamMsg) {
331
+ streamMsg.querySelector('.loading')?.remove();
332
+ streamMsg.dataset.text = `Error: ${error.message || 'An error occurred during the request.'}`;
333
+ renderMarkdown(streamMsg);
334
+ streamMsg.dataset.done = '1';
335
+ streamMsg = null;
336
  }
337
+ console.error('Request error:', error);
338
+ isRequestActive = false;
339
+ abortController = null;
340
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
341
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
342
+ }
343
+
344
+ // Load conversations for sidebar
345
+ async function loadConversations() {
346
+ if (!checkAuth()) return;
347
+ try {
348
+ const response = await fetch('/api/conversations', {
349
+ headers: { 'Authorization': `Bearer ${checkAuth()}` }
350
+ });
351
+ if (!response.ok) throw new Error('Failed to load conversations');
352
+ const conversations = await response.json();
353
+ if (uiElements.conversationList) {
354
+ uiElements.conversationList.innerHTML = '';
355
+ conversations.forEach(conv => {
356
+ const li = document.createElement('li');
357
+ li.className = `flex items-center justify-between text-white hover:bg-gray-700 p-2 rounded cursor-pointer transition-colors ${conv.conversation_id === currentConversationId ? 'bg-gray-700' : ''}`;
358
+ li.dataset.conversationId = conv.conversation_id;
359
+ li.innerHTML = `
360
+ <div class="flex items-center flex-1" data-conversation-id="${conv.conversation_id}">
361
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
362
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
363
+ </svg>
364
+ <span class="truncate flex-1">${conv.title || 'Untitled Conversation'}</span>
365
+ </div>
366
+ <button class="delete-conversation-btn text-red-400 hover:text-red-600 p-1" title="Delete Conversation" data-conversation-id="${conv.conversation_id}">
367
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
368
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-4h4M3 7h18"></path>
369
+ </svg>
370
+ </button>
371
+ `;
372
+ li.querySelector('[data-conversation-id]').addEventListener('click', () => loadConversation(conv.conversation_id));
373
+ li.querySelector('.delete-conversation-btn').addEventListener('click', () => deleteConversation(conv.conversation_id));
374
+ uiElements.conversationList.appendChild(li);
375
+ });
376
+ }
377
+ } catch (error) {
378
+ console.error('Error loading conversations:', error);
379
  }
380
+ }
381
+
382
+ // Load conversation from API
383
+ async function loadConversation(conversationId) {
384
+ try {
385
+ const response = await fetch(`/api/conversations/${conversationId}`, {
386
+ headers: { 'Authorization': `Bearer ${checkAuth()}` }
387
+ });
388
+ if (!response.ok) {
389
+ if (response.status === 401) window.location.href = '/login';
390
+ throw new Error('Failed to load conversation');
391
+ }
392
+ const data = await response.json();
393
+ currentConversationId = data.conversation_id;
394
+ currentConversationTitle = data.title || 'Untitled Conversation';
395
+ conversationHistory = data.messages.map(msg => ({ role: msg.role, content: msg.content }));
396
+ if (uiElements.chatBox) uiElements.chatBox.innerHTML = '';
397
+ conversationHistory.forEach(msg => addMsg(msg.role, msg.content));
398
+ enterChatView();
399
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
400
+ history.pushState(null, '', `/chat/${currentConversationId}`);
401
+ toggleSidebar(false);
402
+ } catch (error) {
403
+ console.error('Error loading conversation:', error);
404
+ alert('Failed to load conversation. Please try again or log in.');
405
  }
406
+ }
407
 
408
+ // Delete conversation
409
+ async function deleteConversation(conversationId) {
410
+ if (!confirm('Are you sure you want to delete this conversation?')) return;
411
+ try {
412
+ const response = await fetch(`/api/conversations/${conversationId}`, {
413
+ method: 'DELETE',
414
+ headers: { 'Authorization': `Bearer ${checkAuth()}` }
415
+ });
416
+ if (!response.ok) {
417
+ if (response.status === 401) window.location.href = '/login';
418
+ throw new Error('Failed to delete conversation');
419
+ }
420
+ if (conversationId === currentConversationId) {
421
+ clearAllMessages();
422
+ currentConversationId = null;
423
+ currentConversationTitle = null;
424
+ history.pushState(null, '', '/chat');
425
+ }
426
+ await loadConversations();
427
+ } catch (error) {
428
+ console.error('Error deleting conversation:', error);
429
+ alert('Failed to delete conversation. Please try again.');
430
+ }
431
+ }
432
 
433
+ // Save message to conversation
434
+ async function saveMessageToConversation(conversationId, role, content) {
435
  try {
436
+ const response = await fetch(`/api/conversations/${conversationId}`, {
 
 
437
  method: 'POST',
438
+ headers: {
439
+ 'Content-Type': 'application/json',
440
+ 'Authorization': `Bearer ${checkAuth()}`
441
+ },
442
+ body: JSON.stringify({ role, content })
443
  });
444
+ if (!response.ok) throw new Error('Failed to save message');
445
+ } catch (error) {
446
+ console.error('Error saving message:', error);
447
+ }
448
+ }
449
 
450
+ // Create new conversation
451
+ async function createNewConversation() {
452
+ if (!checkAuth()) {
453
+ alert('Please log in to create a new conversation.');
454
+ window.location.href = '/login';
455
+ return;
456
+ }
457
+ try {
458
+ const response = await fetch('/api/conversations', {
459
+ method: 'POST',
460
+ headers: {
461
+ 'Content-Type': 'application/json',
462
+ 'Authorization': `Bearer ${checkAuth()}`
463
+ },
464
+ body: JSON.stringify({ title: 'New Conversation' })
465
+ });
466
  if (!response.ok) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  if (response.status === 401) {
468
  localStorage.removeItem('token');
469
  window.location.href = '/login';
 
470
  }
471
+ throw new Error('Failed to create conversation');
472
  }
 
473
  const data = await response.json();
474
+ currentConversationId = data.conversation_id;
475
+ currentConversationTitle = data.title;
476
+ conversationHistory = [];
 
 
 
 
477
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
478
+ if (uiElements.chatBox) uiElements.chatBox.innerHTML = '';
479
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
480
+ history.pushState(null, '', `/chat/${currentConversationId}`);
481
+ enterChatView();
482
+ await loadConversations();
483
+ toggleSidebar(false);
484
+ } catch (error) {
485
+ console.error('Error creating conversation:', error);
486
+ alert('Failed to create new conversation. Please try again.');
487
+ }
488
+ }
489
 
490
+ // Update conversation title
491
+ async function updateConversationTitle(conversationId, newTitle) {
492
+ try {
493
+ const response = await fetch(`/api/conversations/${conversationId}/title`, {
494
+ method: 'PUT',
495
+ headers: {
496
+ 'Content-Type': 'application/json',
497
+ 'Authorization': `Bearer ${checkAuth()}`
498
+ },
499
+ body: JSON.stringify({ title: newTitle })
500
+ });
501
+ if (!response.ok) throw new Error('Failed to update title');
502
+ const data = await response.json();
503
+ currentConversationTitle = data.title;
504
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
505
+ await loadConversations();
506
+ } catch (error) {
507
+ console.error('Error updating title:', error);
508
+ alert('Failed to update conversation title.');
509
+ }
510
+ }
511
+
512
+ // Toggle sidebar
513
+ function toggleSidebar(show) {
514
+ if (uiElements.sidebar) {
515
+ if (window.innerWidth >= 768) {
516
+ isSidebarOpen = true;
517
+ uiElements.sidebar.style.transform = 'translateX(0)';
518
+ if (uiElements.swipeHint) uiElements.swipeHint.style.display = 'none';
519
+ } else {
520
+ isSidebarOpen = show !== undefined ? show : !isSidebarOpen;
521
+ uiElements.sidebar.style.transform = isSidebarOpen ? 'translateX(0)' : 'translateX(-100%)';
522
+ if (uiElements.swipeHint && !isSidebarOpen) {
523
+ uiElements.swipeHint.style.display = 'block';
524
+ setTimeout(() => {
525
+ uiElements.swipeHint.style.display = 'none';
526
+ }, 3000);
527
+ } else if (uiElements.swipeHint) {
528
+ uiElements.swipeHint.style.display = 'none';
529
+ }
530
  }
531
+ }
532
+ }
533
+
534
+ // Setup touch gestures with Hammer.js
535
+ function setupTouchGestures() {
536
+ if (!uiElements.sidebar) return;
537
+ const hammer = new Hammer(uiElements.sidebar);
538
+ const mainContent = document.querySelector('.flex-1');
539
+ const hammerMain = new Hammer(mainContent);
540
+
541
+ hammer.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL });
542
+ hammer.on('pan', e => {
543
+ if (!isSidebarOpen) return;
544
+ let translateX = Math.max(-uiElements.sidebar.offsetWidth, Math.min(0, e.deltaX));
545
+ uiElements.sidebar.style.transform = `translateX(${translateX}px)`;
546
+ });
547
+ hammer.on('panend', e => {
548
+ if (!isSidebarOpen) return;
549
+ if (e.deltaX < -50) {
550
+ toggleSidebar(false);
551
+ } else {
552
+ toggleSidebar(true);
553
  }
554
+ });
555
+
556
+ hammerMain.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL });
557
+ hammerMain.on('panstart', e => {
558
+ if (isSidebarOpen) return;
559
+ if (e.center.x < 50 || e.center.x > window.innerWidth - 50) {
560
+ uiElements.sidebar.style.transition = 'none';
 
 
 
561
  }
562
+ });
563
+ hammerMain.on('pan', e => {
564
+ if (isSidebarOpen) return;
565
+ if (e.center.x < 50 || e.center.x > window.innerWidth - 50) {
566
+ let translateX = e.center.x < 50
567
+ ? Math.min(uiElements.sidebar.offsetWidth, Math.max(0, e.deltaX))
568
+ : Math.max(-uiElements.sidebar.offsetWidth, Math.min(0, e.deltaX));
569
+ uiElements.sidebar.style.transform = `translateX(${translateX - uiElements.sidebar.offsetWidth}px)`;
570
  }
571
+ });
572
+ hammerMain.on('panend', e => {
573
+ uiElements.sidebar.style.transition = 'transform 0.3s ease-in-out';
574
+ if (e.center.x < 50 && e.deltaX > 50) {
575
+ toggleSidebar(true);
576
+ } else if (e.center.x > window.innerWidth - 50 && e.deltaX < -50) {
577
+ toggleSidebar(true);
578
+ } else {
579
+ toggleSidebar(false);
580
  }
581
+ });
582
  }
583
+
584
+ // Send user message
585
  async function submitMessage() {
586
  if (isRequestActive || isRecording) return;
587
+ let message = uiElements.input?.value.trim() || '';
588
+ let payload = null;
589
+ let formData = null;
590
  let endpoint = '/api/chat';
591
+ let headers = {};
592
  let inputType = 'text';
593
  let outputFormat = 'text';
594
+ let title = null;
595
 
596
+ if (uiElements.fileInput?.files.length > 0) {
597
+ const file = uiElements.fileInput.files[0];
598
  if (file.type.startsWith('image/')) {
599
  endpoint = '/api/image-analysis';
600
  inputType = 'image';
601
  message = 'Analyze this image';
602
+ formData = new FormData();
603
  formData.append('file', file);
604
  formData.append('output_format', 'text');
605
  }
606
+ } else if (uiElements.audioInput?.files.length > 0) {
607
+ const file = uiElements.audioInput.files[0];
608
  if (file.type.startsWith('audio/')) {
609
  endpoint = '/api/audio-transcription';
610
  inputType = 'audio';
611
  message = 'Transcribe this audio';
612
+ formData = new FormData();
613
  formData.append('file', file);
614
  }
615
  } else if (message) {
616
+ payload = {
617
+ message,
618
+ system_prompt: 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.',
619
+ history: conversationHistory,
620
+ temperature: 0.7,
621
+ max_new_tokens: 128000,
622
+ enable_browsing: true,
623
+ output_format: 'text',
624
+ title: title
625
+ };
626
+ headers['Content-Type'] = 'application/json';
627
  } else {
628
  return;
629
  }
 
636
  const loadingEl = document.createElement('span');
637
  loadingEl.className = 'loading';
638
  streamMsg.appendChild(loadingEl);
639
+ updateUIForRequest();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
 
641
  isRequestActive = true;
642
  abortController = new AbortController();
643
 
644
  try {
645
+ const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
 
 
 
 
 
 
 
 
646
  if (!response.ok) {
647
  if (response.status === 403) {
648
+ if (uiElements.messageLimitWarning) uiElements.messageLimitWarning.classList.remove('hidden');
649
+ if (uiElements.input) uiElements.input.disabled = true;
650
+ if (streamMsg) streamMsg.querySelector('.loading')?.remove();
651
+ streamMsg = null;
 
 
 
 
 
 
 
652
  isRequestActive = false;
653
  abortController = null;
654
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
655
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
656
+ setTimeout(() => window.location.href = '/login', 3000);
 
 
 
 
 
 
657
  return;
658
  }
659
  if (response.status === 401) {
 
661
  window.location.href = '/login';
662
  return;
663
  }
664
+ throw new Error(`Request failed with status ${response.status}`);
665
  }
666
 
667
  if (endpoint === '/api/audio-transcription') {
668
  const data = await response.json();
669
+ if (!data.transcription) throw new Error('No transcription received from server');
670
  const transcription = data.transcription || 'Error: No transcription generated.';
671
  if (streamMsg) {
672
  streamMsg.dataset.text = transcription;
 
675
  }
676
  conversationHistory.push({ role: 'assistant', content: transcription });
677
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
678
+ if (checkAuth() && currentConversationId) {
679
+ await saveMessageToConversation(currentConversationId, 'assistant', transcription);
680
+ }
681
+ if (checkAuth() && data.conversation_id) {
682
+ currentConversationId = data.conversation_id;
683
+ currentConversationTitle = data.conversation_title || 'Untitled Conversation';
684
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
685
+ history.pushState(null, '', `/chat/${currentConversationId}`);
686
+ await loadConversations();
687
+ }
688
  } else if (endpoint === '/api/image-analysis') {
689
  const data = await response.json();
690
  const analysis = data.image_analysis || 'Error: No analysis generated.';
 
695
  }
696
  conversationHistory.push({ role: 'assistant', content: analysis });
697
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
698
+ if (checkAuth() && currentConversationId) {
699
+ await saveMessageToConversation(currentConversationId, 'assistant', analysis);
700
+ }
701
+ if (checkAuth() && data.conversation_id) {
702
+ currentConversationId = data.conversation_id;
703
+ currentConversationTitle = data.conversation_title || 'Untitled Conversation';
704
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
705
+ history.pushState(null, '', `/chat/${currentConversationId}`);
706
+ await loadConversations();
707
+ }
708
  } else {
709
+ const contentType = response.headers.get('Content-Type');
710
+ if (contentType?.includes('application/json')) {
711
+ const data = await response.json();
712
+ const responseText = data.response || 'Error: No response generated.';
 
 
 
 
713
  if (streamMsg) {
714
+ streamMsg.dataset.text = responseText;
 
 
 
715
  renderMarkdown(streamMsg);
716
+ streamMsg.dataset.done = '1';
717
+ }
718
+ conversationHistory.push({ role: 'assistant', content: responseText });
719
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
720
+ if (checkAuth() && currentConversationId) {
721
+ await saveMessageToConversation(currentConversationId, 'assistant', responseText);
722
+ }
723
+ if (checkAuth() && data.conversation_id) {
724
+ currentConversationId = data.conversation_id;
725
+ currentConversationTitle = data.conversation_title || 'Untitled Conversation';
726
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
727
+ history.pushState(null, '', `/chat/${currentConversationId}`);
728
+ await loadConversations();
729
+ }
730
+ } else {
731
+ const reader = response.body.getReader();
732
+ const decoder = new TextDecoder();
733
+ let buffer = '';
734
+ while (true) {
735
+ const { done, value } = await reader.read();
736
+ if (done) break;
737
+ buffer += decoder.decode(value, { stream: true });
738
+ if (streamMsg) {
739
+ streamMsg.dataset.text = buffer;
740
+ currentAssistantText = buffer;
741
+ streamMsg.querySelector('.loading')?.remove();
742
+ renderMarkdown(streamMsg);
743
+ if (uiElements.chatBox) uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
744
  }
745
  }
746
+ if (streamMsg) streamMsg.dataset.done = '1';
747
+ conversationHistory.push({ role: 'assistant', content: buffer });
748
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
749
+ if (checkAuth() && currentConversationId) {
750
+ await saveMessageToConversation(currentConversationId, 'assistant', buffer);
751
+ }
752
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  }
754
+ finalizeRequest();
755
  } catch (error) {
756
+ handleRequestError(error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  }
758
  }
759
+
760
+ // Stop streaming
761
  function stopStream(forceCancel = false) {
762
  if (!isRequestActive && !isRecording) return;
763
+ if (isRecording) stopVoiceRecording();
 
 
764
  isRequestActive = false;
765
  if (abortController) {
766
  abortController.abort();
767
  abortController = null;
768
  }
769
  if (streamMsg && !forceCancel) {
770
+ streamMsg.querySelector('.loading')?.remove();
 
771
  streamMsg.dataset.text += '';
772
  renderMarkdown(streamMsg);
773
  streamMsg.dataset.done = '1';
774
  streamMsg = null;
775
  }
776
+ if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
777
+ if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
778
+ if (uiElements.stopBtn) uiElements.stopBtn.style.pointerEvents = 'auto';
 
 
 
 
 
 
779
  }
780
 
781
+ // Settings Modal
782
+ if (uiElements.settingsBtn) {
783
+ uiElements.settingsBtn.addEventListener('click', () => {
784
+ if (!checkAuth()) {
785
+ alert('Please log in to access settings.');
786
+ window.location.href = '/login';
787
+ return;
788
+ }
789
+ uiElements.settingsModal.classList.remove('hidden');
790
+ fetch('/api/settings', { // تعديل: استخدام /api/settings بدل /users/me
791
+ headers: { 'Authorization': `Bearer ${checkAuth()}` }
792
+ })
793
+ .then(res => {
794
+ if (!res.ok) {
795
+ if (res.status === 401) {
796
+ localStorage.removeItem('token');
797
+ window.location.href = '/login';
798
+ }
799
+ throw new Error('Failed to fetch settings');
800
+ }
801
+ return res.json();
802
+ })
803
+ .then(data => {
804
+ // تعبئة حقول الـ form
805
+ document.getElementById('display_name').value = data.user_settings.display_name || '';
806
+ document.getElementById('preferred_model').value = data.user_settings.preferred_model || 'standard';
807
+ document.getElementById('job_title').value = data.user_settings.job_title || '';
808
+ document.getElementById('education').value = data.user_settings.education || '';
809
+ document.getElementById('interests').value = data.user_settings.interests || '';
810
+ document.getElementById('additional_info').value = data.user_settings.additional_info || '';
811
+ document.getElementById('conversation_style').value = data.user_settings.conversation_style || 'default';
812
+
813
+ // تعبئة خيارات preferred_model ديناميكيًا
814
+ const modelSelect = document.getElementById('preferred_model');
815
+ modelSelect.innerHTML = ''; // تفريغ الخيارات القديمة
816
+ data.available_models.forEach(model => {
817
+ const option = document.createElement('option');
818
+ option.value = model.alias;
819
+ option.textContent = `${model.alias} - ${model.description}`;
820
+ modelSelect.appendChild(option);
821
+ });
822
+
823
+ // تعبئة خيارات conversation_style ديناميكيًا
824
+ const styleSelect = document.getElementById('conversation_style');
825
+ styleSelect.innerHTML = ''; // تفريغ الخيارات القديمة
826
+ data.conversation_styles.forEach(style => {
827
+ const option = document.createElement('option');
828
+ option.value = style;
829
+ option.textContent = style.charAt(0).toUpperCase() + style.slice(1); // تحويل الحرف الأول لكابيتال
830
+ styleSelect.appendChild(option);
831
+ });
832
+ })
833
+ .catch(err => {
834
+ console.error('Error fetching settings:', err);
835
+ alert('Failed to load settings. Please try again.');
836
+ });
837
  });
838
+ }
839
 
840
+ if (uiElements.closeSettingsBtn) {
841
+ uiElements.closeSettingsBtn.addEventListener('click', () => {
842
+ uiElements.settingsModal.classList.add('hidden');
 
 
 
843
  });
844
  }
845
+
846
+ if (uiElements.settingsForm) {
847
+ uiElements.settingsForm.addEventListener('submit', (e) => {
848
+ e.preventDefault();
849
+ if (!checkAuth()) {
850
+ alert('Please log in to save settings.');
851
+ window.location.href = '/login';
852
+ return;
853
+ }
854
+ const formData = new FormData(uiElements.settingsForm);
855
+ const data = Object.fromEntries(formData);
856
+ fetch('/users/me', {
857
+ method: 'PUT',
858
+ headers: {
859
+ 'Content-Type': 'application/json',
860
+ 'Authorization': `Bearer ${checkAuth()}`
861
+ },
862
+ body: JSON.stringify(data)
863
+ })
864
+ .then(res => {
865
+ if (!res.ok) {
866
+ if (res.status === 401) {
867
+ localStorage.removeItem('token');
868
+ window.location.href = '/login';
869
+ }
870
+ throw new Error('Failed to update settings');
871
+ }
872
+ return res.json();
873
+ })
874
+ .then(() => {
875
+ alert('Settings updated successfully!');
876
+ uiElements.settingsModal.classList.add('hidden');
877
+ toggleSidebar(false);
878
+ })
879
+ .catch(err => {
880
+ console.error('Error updating settings:', err);
881
+ alert('Error updating settings: ' + err.message);
882
+ });
883
  });
884
  }
 
 
 
 
 
 
885
 
886
+ // History Toggle
887
+ if (uiElements.historyToggle) {
888
+ uiElements.historyToggle.addEventListener('click', () => {
889
+ if (uiElements.conversationList) {
890
+ uiElements.conversationList.classList.toggle('hidden');
891
+ uiElements.historyToggle.innerHTML = uiElements.conversationList.classList.contains('hidden')
892
+ ? `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
893
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
894
+ </svg>Show History`
895
+ : `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
896
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
897
+ </svg>Hide History`;
898
  }
899
  });
900
+ }
901
+
902
+ // Event listeners
903
+ uiElements.promptItems.forEach(p => {
904
+ p.addEventListener('click', e => {
905
+ e.preventDefault();
906
+ if (uiElements.input) {
907
+ uiElements.input.value = p.dataset.prompt;
908
+ autoResizeTextarea();
909
  }
910
+ if (uiElements.sendBtn) uiElements.sendBtn.disabled = false;
911
+ submitMessage();
912
+ });
913
+ });
914
+
915
+ if (uiElements.fileBtn) uiElements.fileBtn.addEventListener('click', () => uiElements.fileInput?.click());
916
+ if (uiElements.audioBtn) uiElements.audioBtn.addEventListener('click', () => uiElements.audioInput?.click());
917
+ if (uiElements.fileInput) uiElements.fileInput.addEventListener('change', previewFile);
918
+ if (uiElements.audioInput) uiElements.audioInput.addEventListener('change', previewFile);
919
+
920
+ if (uiElements.sendBtn) {
921
+ uiElements.sendBtn.addEventListener('mousedown', e => {
922
+ if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
923
+ startVoiceRecording();
924
  });
925
+ uiElements.sendBtn.addEventListener('mouseup', () => isRecording && stopVoiceRecording());
926
+ uiElements.sendBtn.addEventListener('mouseleave', () => isRecording && stopVoiceRecording());
927
+ uiElements.sendBtn.addEventListener('touchstart', e => {
928
  e.preventDefault();
929
+ if (uiElements.sendBtn.disabled || isRequestActive || isRecording) return;
930
  startVoiceRecording();
931
  });
932
+ uiElements.sendBtn.addEventListener('touchend', e => {
933
  e.preventDefault();
934
+ if (isRecording) stopVoiceRecording();
 
 
935
  });
936
+ uiElements.sendBtn.addEventListener('touchcancel', e => {
937
  e.preventDefault();
938
+ if (isRecording) stopVoiceRecording();
 
 
939
  });
940
  }
941
 
942
+ if (uiElements.form) {
943
+ uiElements.form.addEventListener('submit', e => {
 
944
  e.preventDefault();
945
+ if (!isRecording) submitMessage();
 
 
946
  });
947
  }
948
 
949
+ if (uiElements.input) {
950
+ uiElements.input.addEventListener('keydown', e => {
 
951
  if (e.key === 'Enter' && !e.shiftKey) {
952
  e.preventDefault();
953
+ if (!isRecording && !uiElements.sendBtn.disabled) submitMessage();
 
 
954
  }
955
  });
956
+ uiElements.input.addEventListener('input', () => {
 
 
957
  autoResizeTextarea();
958
+ updateSendButtonState();
 
 
959
  });
960
  }
961
 
962
+ if (uiElements.stopBtn) {
963
+ uiElements.stopBtn.addEventListener('click', () => {
964
+ uiElements.stopBtn.style.pointerEvents = 'none';
 
965
  stopStream();
966
  });
967
  }
968
 
969
+ if (uiElements.clearBtn) uiElements.clearBtn.addEventListener('click', clearAllMessages);
970
+
971
+ if (uiElements.conversationTitle) {
972
+ uiElements.conversationTitle.addEventListener('click', () => {
973
+ if (!checkAuth()) return alert('Please log in to edit the conversation title.');
974
+ const newTitle = prompt('Enter new conversation title:', currentConversationTitle || '');
975
+ if (newTitle && currentConversationId) {
976
+ updateConversationTitle(currentConversationId, newTitle);
977
+ }
978
  });
979
  }
980
 
981
+ if (uiElements.sidebarToggle) {
982
+ uiElements.sidebarToggle.addEventListener('click', () => toggleSidebar());
 
 
 
983
  }
984
 
985
+ if (uiElements.newConversationBtn) {
986
+ uiElements.newConversationBtn.addEventListener('click', createNewConversation);
 
 
 
987
  }
static/js/sw.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // static/js/sw.js
2
+ // SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ self.addEventListener('install', (event) => {
6
+ event.waitUntil(
7
+ caches.open('ChatMG-v1').then((cache) => {
8
+ return cache.addAll([
9
+ '/',
10
+ '/chat',
11
+ '/static/css/style.css',
12
+ '/static/css/chat/style.css',
13
+ '/static/css/sidebar.css',
14
+ '/static/js/chat.js',
15
+ '/static/images/mg.svg',
16
+ '/static/images/icons/mg-48.png',
17
+ '/static/images/icons/mg-72.png',
18
+ '/static/images/icons/mg-96.png',
19
+ '/static/images/icons/mg-128.png',
20
+ '/static/images/icons/mg-192.png',
21
+ '/static/images/icons/mg-256.png',
22
+ '/static/images/icons/mg-384.png',
23
+ '/static/images/icons/mg-512.png'
24
+ ]);
25
+ })
26
+ );
27
+ });
28
+ self.addEventListener('activate', (event) => {
29
+ event.waitUntil(
30
+ caches.keys().then((cacheNames) => {
31
+ return Promise.all(
32
+ cacheNames.filter((name) => name !== 'ChatMG-v1').map((name) => caches.delete(name))
33
+ );
34
+ })
35
+ );
36
+ });
37
+
38
+ self.addEventListener('fetch', (event) => {
39
+ event.respondWith(
40
+ caches.match(event.request).then((response) => {
41
+ return response || fetch(event.request);
42
+ })
43
+ );
44
+ });
static/manifest.json ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "MGZon Chatbot",
3
+ "short_name": "ChatMG",
4
+ "description": "AI-powered chatbot for coding, analysis, and e-commerce queries.",
5
+ "start_url": "/chat",
6
+ "display": "standalone",
7
+ "background_color": "#1a202c",
8
+ "theme_color": "#2d3748",
9
+ "scope": "/",
10
+ "icons": [
11
+ {
12
+ "src": "/static/images/mg.svg",
13
+ "sizes": "192x192",
14
+ "type": "image/svg+xml"
15
+ },
16
+ {
17
+ "src": "/static/images/icons/mg-48.png",
18
+ "sizes": "48x48",
19
+ "type": "image/png"
20
+ },
21
+ {
22
+ "src": "/static/images/icons/mg-72.png",
23
+ "sizes": "72x72",
24
+ "type": "image/png"
25
+ },
26
+ {
27
+ "src": "/static/images/icons/mg-96.png",
28
+ "sizes": "96x96",
29
+ "type": "image/png"
30
+ },
31
+ {
32
+ "src": "/static/images/icons/mg-128.png",
33
+ "sizes": "128x128",
34
+ "type": "image/png"
35
+ },
36
+ {
37
+ "src": "/static/images/icons/mg-192.png",
38
+ "sizes": "192x192",
39
+ "type": "image/png"
40
+ },
41
+ {
42
+ "src": "/static/images/icons/mg-256.png",
43
+ "sizes": "256x256",
44
+ "type": "image/png"
45
+ },
46
+ {
47
+ "src": "/static/images/icons/mg-384.png",
48
+ "sizes": "384x384",
49
+ "type": "image/png"
50
+ },
51
+ {
52
+ "src": "/static/images/icons/mg-512.png",
53
+ "sizes": "512x512",
54
+ "type": "image/png"
55
+ }
56
+ ]
57
+ }
templates/500.html CHANGED
@@ -7,6 +7,9 @@
7
  <title>500 - Server Error</title>
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
 
 
 
10
  </head>
11
  <body class="bg-gray-900 text-white min-h-screen flex flex-col">
12
  <div class="container max-w-6xl mx-auto text-center py-12 flex-1">
@@ -28,5 +31,15 @@
28
  </div>
29
  </footer>
30
  <script src="/static/js/scripts.js"></script>
 
 
 
 
 
 
 
 
 
 
31
  </body>
32
  </html>
 
7
  <title>500 - Server Error</title>
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/boxicons.min.css" rel="stylesheet">
10
+ <link rel="icon" href="/static/images/icons/mg-192.png" sizes="192x192">
11
+ <link rel="manifest" href="/static/manifest.json">
12
+
13
  </head>
14
  <body class="bg-gray-900 text-white min-h-screen flex flex-col">
15
  <div class="container max-w-6xl mx-auto text-center py-12 flex-1">
 
31
  </div>
32
  </footer>
33
  <script src="/static/js/scripts.js"></script>
34
+ <script>
35
+ if ('serviceWorker' in navigator) {
36
+ navigator.serviceWorker.register('/static/js/sw.js')
37
+ .then(function(reg) {
38
+ console.log('✅ Service Worker Registered', reg);
39
+ }).catch(function(err) {
40
+ console.error('❌ Service Worker registration failed', err);
41
+ });
42
+ }
43
+ </script>
44
  </body>
45
  </html>
templates/blog_post.html CHANGED
@@ -6,6 +6,8 @@
6
  <meta name="description" content="{{ post.title }} - Read about AI, coding, and e-commerce trends from MGZon AI by Mark Al-Asfar.">
7
  <meta name="keywords" content="MGZon Chatbot, MGZon AI, blog, AI trends, code generation, e-commerce, Mark Al-Asfar, AI chatbot, code generation bot, e-commerce chatbot, Python AI chatbot, AI for coding, e-commerce automation, 2025 AI trends, chatgpt, grok, deepseek, text generation">
8
  <meta name="author" content="Mark Al-Asfar">
 
 
9
  <meta name="robots" content="index, follow">
10
  <title>{{ post.title }} - MGZon AI</title>
11
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
 
6
  <meta name="description" content="{{ post.title }} - Read about AI, coding, and e-commerce trends from MGZon AI by Mark Al-Asfar.">
7
  <meta name="keywords" content="MGZon Chatbot, MGZon AI, blog, AI trends, code generation, e-commerce, Mark Al-Asfar, AI chatbot, code generation bot, e-commerce chatbot, Python AI chatbot, AI for coding, e-commerce automation, 2025 AI trends, chatgpt, grok, deepseek, text generation">
8
  <meta name="author" content="Mark Al-Asfar">
9
+ <link rel="manifest" href="/static/manifest.json">
10
+
11
  <meta name="robots" content="index, follow">
12
  <title>{{ post.title }} - MGZon AI</title>
13
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
templates/chat.html CHANGED
@@ -13,6 +13,18 @@
13
  <link rel="apple-touch-icon" href="/static/images/mg.svg" />
14
  <!-- Open Graph -->
15
  <meta property="og:title" content="MGZon Chatbot – AI Assistant" />
 
 
 
 
 
 
 
 
 
 
 
 
16
  <meta property="og:description" content="Chat with MGZon Chatbot for coding, analysis, and e-commerce queries with text, image, and audio support." />
17
  <meta property="og:image" content="/static/images/mg.svg" />
18
  <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/chat" />
@@ -51,13 +63,14 @@
51
  <!-- Smooth Scroll -->
52
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.js"></script>
53
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.css" />
 
 
54
  <!-- Project-Specific Styles -->
55
  <link rel="stylesheet" href="/static/css/animation/style.css" />
56
  <link rel="stylesheet" href="/static/css/button.css" />
57
  <link rel="stylesheet" href="/static/css/chat/bubble.css" />
58
  <link rel="stylesheet" href="/static/css/chat/markdown.css" />
59
  <link rel="stylesheet" href="/static/css/chat/style.css" />
60
- <link rel="stylesheet" href="/static/css/footer.css" />
61
  <link rel="stylesheet" href="/static/css/header.css" />
62
  <link rel="stylesheet" href="/static/css/icon.css" />
63
  <link rel="stylesheet" href="/static/css/input.css" />
@@ -74,182 +87,332 @@
74
  <link rel="stylesheet" href="/static/css/screen/common.css" />
75
  <link rel="stylesheet" href="/static/css/style.css" />
76
  <link rel="stylesheet" href="/static/css/webkit.css" />
 
77
  </head>
78
  <body class="min-h-screen flex flex-col bg-gradient-to-br from-gray-900 via-teal-900 to-emerald-900">
79
- <!-- Main Header -->
80
- <header id="mainHeader" class="main-header p-4 flex justify-between items-center">
81
- <div class="logo flex items-center">
82
- <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-16 h-16 mr-2 animate-pulse" />
83
- <h1 class="text-2xl font-bold text-white">MGZon Chatbot</h1>
84
- </div>
85
- <div class="flex items-center gap-4">
86
- <a href="/" class="icon-btn" aria-label="Home" title="Back to Home">
87
- <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">
88
- <path d="M3 11.5L12 4l9 7.5" />
89
- <path d="M5 21V11.5h14V21" />
90
- </svg>
91
- </a>
92
- {% if not user %}
93
- <a href="/login" class="bg-gradient-to-r from-blue-500 to-cyan-600 text-white px-4 py-2 rounded font-semibold hover:scale-105 transition-transform">Login</a>
94
- {% endif %}
95
- </div>
96
- </header>
97
-
98
- <!-- Chat Header -->
99
- <header class="chat-header p-3" id="chatHeader" aria-hidden="true">
100
- <div class="chat-title text-xl font-semibold text-white">MGZon AI Assistant</div>
101
- <div class="chat-controls flex gap-2">
102
- <button id="clearBtn" class="icon-btn" aria-label="Clear All Messages" title="Clear All Messages">
103
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
104
- <path d="M3 6h18" />
105
- <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
106
- <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
107
- <path d="M10 11v6" />
108
- <path d="M14 11v6" />
109
  </svg>
110
  </button>
111
  </div>
112
- </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  <!-- Main Content -->
115
- <main class="flex-1 flex flex-col max-w-screen-xl w-full mx-auto">
116
- <div id="initialContent" class="flex flex-col items-center justify-center text-center h-full">
117
- <div class="title mb-4 gradient-text text-3xl font-bold">
118
- How can I help you today?
 
119
  </div>
120
- <p class="system text-gray-300 mb-4">
121
- A versatile chatbot powered by DeepSeek, GPT-OSS, CLIP, Whisper, and TTS.<br>
122
- Type your query, upload images/files, or hold the send button to record audio!
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>
149
- <span>Convert this text to audio</span>
150
- </div>
151
  </div>
152
- <div id="messageLimitWarning" class="text-red-500 text-center hidden mt-4">
153
- You have reached the message limit. Please <a href="/login" class="text-blue-300 underline">login</a> to continue.
154
- </div>
155
- </div>
156
- <div id="chatArea" class="flex-1 hidden" aria-live="polite">
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">
166
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
167
- <path d="M14 2v6h6" />
 
 
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" />
175
- <path d="M12 19v4" />
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 or Hold to Record" title="Click to Send or Hold to Record Voice">
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" />
182
  </svg>
183
- </button>
184
- <button id="stopBtn" class="icon-btn" aria-label="Stop" title="Stop" style="display: none;">
185
- <svg width="20" height="20" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
186
- <rect x="6" y="6" width="12" height="12" rx="2" fill="white" />
 
187
  </svg>
188
- </button>
 
 
 
 
189
  </div>
190
  </div>
191
- <div id="filePreview" class="upload-preview" style="display: none;"></div>
192
- <div id="audioPreview" class="audio-preview" style="display: none;"></div>
193
- </form>
194
- </main>
195
-
196
- <!-- Footer -->
197
- <footer class="bg-gradient-to-r from-teal-900 to-emerald-900 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-16 h-16 mx-auto mb-4 animate-pulse">
200
- <p class="mb-2 text-sm">
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-4">
205
- <div class="glass p-3 cursor-pointer" onclick="showCardDetails('email')">
206
- <i class="bx bx-mail-send text-2xl text-emerald-300 mb-1"></i>
207
- <h4 class="font-semibold mb-1 text-sm">Email Us</h4>
208
- <p class="text-xs"><a href="mailto:[email protected]" class="text-emerald-300 hover:underline">[email protected]</a></p>
209
- <div id="email-details" class="hidden mt-2 p-3 bg-gray-700/80 rounded-lg">
210
- <p class="text-xs">Reach out to our support team for any inquiries or assistance.</p>
211
- <button onclick="closeCardDetails('email')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
 
 
 
 
 
 
 
 
 
 
 
 
212
  </div>
213
  </div>
214
- <div class="glass p-3 cursor-pointer" onclick="showCardDetails('phone')">
215
- <i class="bx bx-phone text-2xl text-emerald-300 mb-1"></i>
216
- <h4 class="font-semibold mb-1 text-sm">Phone Support</h4>
217
- <p class="text-xs">+1-800-123-4567</p>
218
- <div id="phone-details" class="hidden mt-2 p-3 bg-gray-700/80 rounded-lg">
219
- <p class="text-xs">Contact our support team via phone for immediate assistance.</p>
220
- <button onclick="closeCardDetails('phone')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
221
- </div>
 
 
 
 
 
 
 
222
  </div>
223
- <div class="glass p-3 cursor-pointer" onclick="showCardDetails('community')">
224
- <i class="bx bx-group text-2xl text-emerald-300 mb-1"></i>
225
- <h4 class="font-semibold mb-1 text-sm">Community</h4>
226
- <p class="text-xs"><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-2 p-3 bg-gray-700/80 rounded-lg">
228
- <p class="text-xs">Join our vibrant community to share ideas and collaborate.</p>
229
- <button onclick="closeCardDetails('community')" class="bg-emerald-500 text-white px-3 py-1 rounded-lg mt-2 text-xs">Close</button>
230
  </div>
231
- </div>
232
- </div>
233
- <div class="flex justify-center gap-4 mt-4">
234
- <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>
235
- <a href="https://x.com/MGZon" class="text-xl 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-xl text-white hover:text-emerald-300 transition"><i class="bx bxl-facebook"></i></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  </div>
238
- <p class="mt-4 text-xs">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
239
  </div>
240
- </footer>
 
 
 
 
 
 
 
 
241
 
242
  <!-- Scripts -->
 
243
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
244
- <script src="/static/js/chat.js"></script>
245
- <script>
246
- AOS.init();
247
- function showCardDetails(cardId) {
248
- document.getElementById(`${cardId}-details`).classList.remove('hidden');
 
 
 
 
 
 
249
  }
250
- function closeCardDetails(cardId) {
251
- document.getElementById(`${cardId}-details`).classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  }
253
- </script>
 
 
254
  </body>
255
  </html>
 
13
  <link rel="apple-touch-icon" href="/static/images/mg.svg" />
14
  <!-- Open Graph -->
15
  <meta property="og:title" content="MGZon Chatbot – AI Assistant" />
16
+ <!-- manifest for Android/Chrome -->
17
+ <link rel="manifest" href="/static/manifest.json">
18
+
19
+ <!-- iOS Web App Support -->
20
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/images/icons/mg-180.png">
21
+ <meta name="apple-mobile-web-app-capable" content="yes">
22
+ <meta name="apple-mobile-web-app-title" content="MGZon">
23
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
24
+
25
+ <!-- General Theme -->
26
+ <meta name="theme-color" content="#2d3748">
27
+
28
  <meta property="og:description" content="Chat with MGZon Chatbot for coding, analysis, and e-commerce queries with text, image, and audio support." />
29
  <meta property="og:image" content="/static/images/mg.svg" />
30
  <meta property="og:url" content="https://mgzon-mgzon-app.hf.space/chat" />
 
63
  <!-- Smooth Scroll -->
64
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.js"></script>
65
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/locomotive-scroll.min.css" />
66
+ <!-- Hammer.js for touch gestures -->
67
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/hammer.min.js"></script>
68
  <!-- Project-Specific Styles -->
69
  <link rel="stylesheet" href="/static/css/animation/style.css" />
70
  <link rel="stylesheet" href="/static/css/button.css" />
71
  <link rel="stylesheet" href="/static/css/chat/bubble.css" />
72
  <link rel="stylesheet" href="/static/css/chat/markdown.css" />
73
  <link rel="stylesheet" href="/static/css/chat/style.css" />
 
74
  <link rel="stylesheet" href="/static/css/header.css" />
75
  <link rel="stylesheet" href="/static/css/icon.css" />
76
  <link rel="stylesheet" href="/static/css/input.css" />
 
87
  <link rel="stylesheet" href="/static/css/screen/common.css" />
88
  <link rel="stylesheet" href="/static/css/style.css" />
89
  <link rel="stylesheet" href="/static/css/webkit.css" />
90
+ <link rel="stylesheet" href="/static/css/sidebar.css" />
91
  </head>
92
  <body class="min-h-screen flex flex-col bg-gradient-to-br from-gray-900 via-teal-900 to-emerald-900">
93
+ <!-- Sidebar -->
94
+ <aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-gray-800/90 backdrop-blur-md transform -translate-x-full md:translate-x-0 transition-transform duration-300 ease-in-out z-50">
95
+ <div class="flex items-center justify-between p-4 border-b border-gray-700">
96
+ <div class="flex items-center">
97
+ <img src="/static/images/mg.svg" alt="MGZon Logo" class="w-10 h-10 mr-2 animate-pulse" />
98
+ <h2 class="text-lg font-bold text-white">MGZon</h2>
99
+ </div>
100
+ <button id="sidebarToggle" class="md:hidden text-white" aria-label="Close Sidebar">
101
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  </svg>
104
  </button>
105
  </div>
106
+ <nav class="p-4">
107
+ <ul class="space-y-2">
108
+ <li>
109
+ <a href="/" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
110
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
111
+ <path d="M3 11.5L12 4l9 7.5M5 21V11.5h14V21"></path>
112
+ </svg>
113
+ Home
114
+ </a>
115
+ </li>
116
+ <li>
117
+ <a href="/about" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
118
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
119
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
120
+ </svg>
121
+ About
122
+ </a>
123
+ </li>
124
+ <li>
125
+ <a href="/blog" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
126
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
127
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 006 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"></path>
128
+ </svg>
129
+ Blog
130
+ </a>
131
+ </li>
132
+ <li>
133
+ <a href="/docs" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
134
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
135
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
136
+ </svg>
137
+ Docs
138
+ </a>
139
+ </li>
140
+ {% if user %}
141
+ <li>
142
+ <button id="settingsBtn" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors w-full text-left">
143
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
144
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"></path>
145
+ </svg>
146
+ Settings
147
+ </button>
148
+ </li>
149
+ <li>
150
+ <a href="/auth/jwt/logout" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
151
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
152
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
153
+ </svg>
154
+ Logout
155
+ </a>
156
+ </li>
157
+ {% else %}
158
+ <li>
159
+ <a href="/login" class="flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors">
160
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
161
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
162
+ </svg>
163
+ Login
164
+ </a>
165
+ </li>
166
+ {% endif %}
167
+ </ul>
168
+ {% if user %}
169
+ <div class="mt-4">
170
+ <div class="flex justify-between items-center px-2 mb-2">
171
+ <h3 class="text-sm font-semibold text-white">Conversations</h3>
172
+ <button id="newConversationBtn" class="text-white hover:bg-gray-700 p-1 rounded" title="New Conversation">
173
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
174
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
175
+ </svg>
176
+ </button>
177
+ </div>
178
+ <button id="historyToggle" class="md:hidden flex items-center text-white hover:bg-gray-700 p-2 rounded transition-colors w-full text-left">
179
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
180
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
181
+ </svg>
182
+ Show History
183
+ </button>
184
+ <ul id="conversationList" class="space-y-2 max-h-[calc(100vh-300px)] overflow-y-auto hidden md:block">
185
+ <!-- Conversations will be populated by JavaScript -->
186
+ </ul>
187
+ </div>
188
+ {% endif %}
189
+ </nav>
190
+ </aside>
191
+
192
+ <!-- Swipe Hint for Mobile -->
193
+ <div id="swipeHint" class="md:hidden fixed top-1/2 left-4 z-50 animate-pulse">
194
+ <img src="/static/images/swipe-hint.svg" alt="Swipe to open sidebar" class="w-8 h-8" />
195
+ </div>
196
 
197
  <!-- Main Content -->
198
+ <div class="flex-1 flex flex-col max-w-screen-xl w-full mx-auto md:ml-64">
199
+ <!-- Chat Header -->
200
+ <header class="chat-header p-3 flex justify-between items-center">
201
+ <div class="chat-title text-xl font-semibold text-white">
202
+ <span id="conversationTitle">{{ conversation_title | default('MGZon AI Assistant') }}</span>
203
  </div>
204
+ <div class="chat-controls flex gap-2">
205
+ <button id="clearBtn" class="icon-btn" aria-label="Clear All Messages" title="Clear All Messages">
206
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
207
+ <path d="M3 6h18" />
208
+ <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
209
+ <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
210
+ <path d="M10 11v6" />
211
+ <path d="M14 11v6" />
 
 
 
 
 
 
 
 
212
  </svg>
213
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  </div>
215
+ </header>
216
+
217
+ <!-- Chat Area -->
218
+ <main class="flex-1 flex flex-col">
219
+ <div id="initialContent" class="flex flex-col items-center justify-center text-center h-full">
220
+ <div class="title mb-4 gradient-text text-3xl font-bold">
221
+ How can I help you today?
222
+ </div>
223
+ <p class="system text-gray-300 mb-4">
224
+ A versatile chatbot powered by DeepSeek, GPT-OSS, CLIP, Whisper, and TTS.<br>
225
+ Type your query, upload images/files, or hold the send button to record audio!
226
+ </p>
227
+ <!-- Prompts -->
228
+ <div class="prompts w-full max-w-md mx-auto grid gap-2">
229
+ <div class="prompt-item" data-prompt="What's the capital of France?">
230
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
231
+ <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" />
232
+ <circle cx="12" cy="12" r="4" stroke-width="2" />
233
  </svg>
234
+ <span>What's the capital of France?</span>
235
+ </div>
236
+ <div class="prompt-item" data-prompt="Generate a Python script for a simple web server">
237
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
238
+ <path d="M4 7h16M4 12h16M4 17h16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
 
 
239
  </svg>
240
+ <span>Generate a Python script for a simple web server</span>
241
+ </div>
242
+ <div class="prompt-item" data-prompt="Analyze this image for me">
243
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
244
+ <path d="M3 12h18M12 3v18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
245
  </svg>
246
+ <span>Analyze this image for me</span>
247
+ </div>
248
+ <div class="prompt-item" data-prompt="Convert this text to audio">
249
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
250
+ <path d="M12 3v18M7 12h10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
251
  </svg>
252
+ <span>Convert this text to audio</span>
253
+ </div>
254
+ </div>
255
+ <div id="messageLimitWarning" class="text-red-500 text-center hidden mt-4">
256
+ You have reached the message limit. Please <a href="/login" class="text-blue-300 underline">login</a> to continue.
257
  </div>
258
  </div>
259
+ <div id="chatArea" class="flex-1 hidden" aria-live="polite">
260
+ <div id="chatBox" class="flex-col" aria-live="polite"></div>
261
+ </div>
262
+ <!-- Footer Form -->
263
+ <form id="footerForm" class="flex p-4">
264
+ <div id="inputContainer" class="w-full">
265
+ <textarea id="userInput" placeholder="Ask anything..." required></textarea>
266
+ <div id="rightIconGroup" class="flex gap-2">
267
+ <button type="button" id="fileBtn" class="icon-btn" aria-label="Upload File" title="Upload File">
268
+ <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">
269
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
270
+ <path d="M14 2v6h6" />
271
+ </svg>
272
+ </button>
273
+ <input type="file" id="fileInput" accept="image/*,.mp3,.wav" style="display: none;" />
274
+ <button type="button" id="audioBtn" class="icon-btn" aria-label="Upload Audio File" title="Upload Audio File">
275
+ <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">
276
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
277
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
278
+ <path d="M12 19v4" />
279
+ </svg>
280
+ </button>
281
+ <input type="file" id="audioInput" accept="audio/*" style="display: none;" />
282
+ <button type="submit" id="sendBtn" class="icon-btn" disabled aria-label="Send or Hold to Record" title="Click to Send or Hold to Record Voice">
283
+ <svg id="sendIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
284
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7M3 12h11" />
285
+ </svg>
286
+ </button>
287
+ <button id="stopBtn" class="icon-btn" aria-label="Stop" title="Stop" style="display: none;">
288
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
289
+ <rect x="6" y="6" width="12" height="12" rx="2" fill="white" />
290
+ </svg>
291
+ </button>
292
  </div>
293
  </div>
294
+ <div id="filePreview" class="upload-preview" style="display: none;"></div>
295
+ <div id="audioPreview" class="audio-preview" style="display: none;"></div>
296
+ </form>
297
+ </main>
298
+
299
+ <!-- Settings Modal -->
300
+ <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
301
+ <div class="bg-gray-800 p-6 rounded-lg w-full max-w-md">
302
+ <div class="flex justify-between items-center mb-4">
303
+ <h2 class="text-xl font-bold text-white">User Settings</h2>
304
+ <button id="closeSettingsBtn" class="text-gray-400 hover:text-white">
305
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
306
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
307
+ </svg>
308
+ </button>
309
  </div>
310
+ <form id="settingsForm" class="space-y-4">
311
+ <div>
312
+ <label for="display_name" class="block text-sm text-gray-300">Display Name</label>
313
+ <input type="text" id="display_name" name="display_name" class="w-full p-2 bg-gray-700 text-white rounded">
 
 
 
314
  </div>
315
+ <div>
316
+ <label for="preferred_model" class="block text-sm text-gray-300">Preferred Model</label>
317
+ <select id="preferred_model" name="preferred_model" class="w-full p-2 bg-gray-700 text-white rounded">
318
+ <option value="">Default</option>
319
+ <option value="advanced">Advanced (High-performance)</option>
320
+ <option value="standard">Standard (Balanced)</option>
321
+ <option value="light">Light (Quick)</option>
322
+ </select>
323
+ </div>
324
+ <div>
325
+ <label for="job_title" class="block text-sm text-gray-300">Job Title</label>
326
+ <input type="text" id="job_title" name="job_title" class="w-full p-2 bg-gray-700 text-white rounded">
327
+ </div>
328
+ <div>
329
+ <label for="education" class="block text-sm text-gray-300">Education</label>
330
+ <input type="text" id="education" name="education" class="w-full p-2 bg-gray-700 text-white rounded">
331
+ </div>
332
+ <div>
333
+ <label for="interests" class="block text-sm text-gray-300">Interests</label>
334
+ <textarea id="interests" name="interests" class="w-full p-2 bg-gray-700 text-white rounded"></textarea>
335
+ </div>
336
+ <div>
337
+ <label for="additional_info" class="block text-sm text-gray-300">Additional Info</label>
338
+ <textarea id="additional_info" name="additional_info" class="w-full p-2 bg-gray-700 text-white rounded"></textarea>
339
+ </div>
340
+ <div>
341
+ <label for="conversation_style" class="block text-sm text-gray-300">Conversation Style</label>
342
+ <select id="conversation_style" name="conversation_style" class="w-full p-2 bg-gray-700 text-white rounded">
343
+ <option value="default">Default</option>
344
+ <option value="concise">Concise</option>
345
+ <option value="analytical">Analytical</option>
346
+ <option value="creative">Creative</option>
347
+ </select>
348
+ </div>
349
+ <div class="flex justify-end gap-2">
350
+ <button type="button" id="closeSettingsBtn" class="px-4 py-2 bg-gray-600 text-white rounded">Cancel</button>
351
+ <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded">Save</button>
352
+ </div>
353
+ </form>
354
  </div>
 
355
  </div>
356
+
357
+ <!-- Copyright -->
358
+ <div class="text-center text-xs text-gray-400 py-2">
359
+ © 2025 Mark Al-Asfar & MGZon AI. All rights reserved.
360
+ </div>
361
+ <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">
362
+ 📲 Install MG Chat
363
+ </button>
364
+ </div>
365
 
366
  <!-- Scripts -->
367
+ <script src="/static/js/chat.js?v=1.0"></script>
368
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
369
+
370
+ <script>
371
+ // تهيئة مكتبة AOS
372
+ AOS.init();
373
+
374
+ // تمرير conversation_id و conversation_title إذا كانا موجودين
375
+ {% if conversation_id %}
376
+ window.conversationId = "{{ conversation_id }}";
377
+ window.conversationTitle = "{{ conversation_title }}";
378
+ if (window.loadConversation) {
379
+ window.loadConversation("{{ conversation_id }}");
380
  }
381
+ {% endif %}
382
+
383
+ // تسجيل Service Worker لتفعيل الـ PWA
384
+ if ('serviceWorker' in navigator) {
385
+ navigator.serviceWorker.register('/static/js/sw.js')
386
+ .then(function(reg) {
387
+ console.log('✅ Service Worker Registered', reg);
388
+ }).catch(function(err) {
389
+ console.error('❌ Service Worker registration failed', err);
390
+ });
391
+ }
392
+
393
+ // التعامل مع حدث beforeinstallprompt لتثبيت التطبيق
394
+ let deferredPrompt;
395
+ window.addEventListener('beforeinstallprompt', (e) => {
396
+ e.preventDefault();
397
+ deferredPrompt = e;
398
+ const installBtn = document.getElementById('installAppBtn');
399
+ if (installBtn) {
400
+ installBtn.style.display = 'block';
401
+
402
+ installBtn.addEventListener('click', () => {
403
+ deferredPrompt.prompt();
404
+ deferredPrompt.userChoice.then(choice => {
405
+ if (choice.outcome === 'accepted') {
406
+ console.log('✅ User accepted the install prompt');
407
+ } else {
408
+ console.log('❌ User dismissed the install prompt');
409
+ }
410
+ deferredPrompt = null;
411
+ });
412
+ });
413
  }
414
+ });
415
+ </script>
416
+
417
  </body>
418
  </html>
templates/index.html CHANGED
@@ -10,7 +10,18 @@ United States, FastAPI, Gradio">
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
- <link rel="apple-touch-icon" href="/static/images/mg.svg">
 
 
 
 
 
 
 
 
 
 
 
14
  <!-- Open Graph -->
15
  <meta property="og:title" content="MGZon Chatbot – AI-Powered Solutions">
16
  <meta property="og:description" content="Explore MGZon Chatbot for code generation, web search, and e-commerce solutions by Mark Al-Asfar.">
@@ -303,6 +314,9 @@ United States. Ready to code smarter and shop better?
303
  </div>
304
  <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
305
  </div>
 
 
 
306
  </footer>
307
  <script>
308
  const sidebar = document.getElementById('sidebar');
@@ -337,6 +351,38 @@ United States. Ready to code smarter and shop better?
337
  spinner.classList.remove('hidden');
338
  setTimeout(() => spinner.classList.add('hidden'), 2000);
339
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  </script>
341
  </body>
342
  </html>
 
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.">
 
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');
 
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>
templates/login.html CHANGED
@@ -8,8 +8,19 @@
8
  <meta name="author" content="Mark Al-Asfar">
9
  <meta name="robots" content="index, follow">
10
  <title>Login - MGZon Chatbot</title>
 
 
 
 
 
 
 
 
 
 
 
 
11
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
12
- <link rel="apple-touch-icon" href="/static/images/mg.svg">
13
  <!-- Open Graph -->
14
  <meta property="og:title" content="Login - MGZon Chatbot">
15
  <meta property="og:description" content="Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
@@ -96,10 +107,10 @@
96
  </button>
97
  </form>
98
  <div class="flex justify-center gap-4 mt-4 flex-wrap">
99
- <a href="/auth/google/login" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
100
  Login with Google <i class="bx bxl-google ml-2"></i>
101
  </a>
102
- <a href="/auth/github/login" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
103
  Login with GitHub <i class="bx bxl-github ml-2"></i>
104
  </a>
105
  </div>
@@ -177,6 +188,9 @@
177
  </div>
178
  <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
179
  </div>
 
 
 
180
  </footer>
181
  <script>
182
  const loginForm = document.getElementById('loginForm');
@@ -213,6 +227,39 @@
213
  function closeCardDetails(cardId) {
214
  document.getElementById(`${cardId}-details`).classList.add('hidden');
215
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  </script>
217
  </body>
218
  </html>
 
8
  <meta name="author" content="Mark Al-Asfar">
9
  <meta name="robots" content="index, follow">
10
  <title>Login - MGZon Chatbot</title>
11
+ <!-- manifest for Android/Chrome -->
12
+ <link rel="manifest" href="/static/manifest.json">
13
+
14
+ <!-- iOS Web App Support -->
15
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/images/icons/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
+
20
+ <!-- General Theme -->
21
+ <meta name="theme-color" content="#2d3748">
22
+
23
  <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
 
24
  <!-- Open Graph -->
25
  <meta property="og:title" content="Login - MGZon Chatbot">
26
  <meta property="og:description" content="Login to MGZon Chatbot to access AI-powered code generation and e-commerce tools.">
 
107
  </button>
108
  </form>
109
  <div class="flex justify-center gap-4 mt-4 flex-wrap">
110
+ <a href="/auth/google/authorize" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
111
  Login with Google <i class="bx bxl-google ml-2"></i>
112
  </a>
113
+ <a href="/auth/github/authorize" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
114
  Login with GitHub <i class="bx bxl-github ml-2"></i>
115
  </a>
116
  </div>
 
188
  </div>
189
  <p class="mt-6">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
190
  </div>
191
+ <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">
192
+ 📲 Install MG Chat
193
+ </button>
194
  </footer>
195
  <script>
196
  const loginForm = document.getElementById('loginForm');
 
227
  function closeCardDetails(cardId) {
228
  document.getElementById(`${cardId}-details`).classList.add('hidden');
229
  }
230
+
231
+ // تسجيل Service Worker لتفعيل الـ PWA
232
+ if ('serviceWorker' in navigator) {
233
+ navigator.serviceWorker.register('/static/js/sw.js')
234
+ .then(function(reg) {
235
+ console.log('✅ Service Worker Registered', reg);
236
+ }).catch(function(err) {
237
+ console.error('❌ Service Worker registration failed', err);
238
+ });
239
+ }
240
+
241
+ // التعامل مع حدث beforeinstallprompt لتثبيت التطبيق
242
+ let deferredPrompt;
243
+ window.addEventListener('beforeinstallprompt', (e) => {
244
+ e.preventDefault();
245
+ deferredPrompt = e;
246
+ const installBtn = document.getElementById('installAppBtn');
247
+ if (installBtn) {
248
+ installBtn.style.display = 'block';
249
+
250
+ installBtn.addEventListener('click', () => {
251
+ deferredPrompt.prompt();
252
+ deferredPrompt.userChoice.then(choice => {
253
+ if (choice.outcome === 'accepted') {
254
+ console.log('✅ User accepted the install prompt');
255
+ } else {
256
+ console.log('❌ User dismissed the install prompt');
257
+ }
258
+ deferredPrompt = null;
259
+ });
260
+ });
261
+ }
262
+ });
263
  </script>
264
  </body>
265
  </html>
templates/register.html CHANGED
@@ -92,10 +92,10 @@
92
  </button>
93
  </form>
94
  <div class="flex justify-center gap-4 mt-4 flex-wrap">
95
- <a href="/auth/google/login" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
96
  Sign Up with Google <i class="bx bxl-google ml-2"></i>
97
  </a>
98
- <a href="/auth/github/login" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
99
  Sign Up with GitHub <i class="bx bxl-github ml-2"></i>
100
  </a>
101
  </div>
@@ -208,6 +208,14 @@
208
  }
209
  function closeCardDetails(cardId) {
210
  document.getElementById(`${cardId}-details`).classList.add('hidden');
 
 
 
 
 
 
 
 
211
  }
212
  </script>
213
  </body>
 
92
  </button>
93
  </form>
94
  <div class="flex justify-center gap-4 mt-4 flex-wrap">
95
+ <a href="/auth/google/authorize" class="inline-flex items-center bg-gradient-to-r from-white to-gray-200 text-gray-800 px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
96
  Sign Up with Google <i class="bx bxl-google ml-2"></i>
97
  </a>
98
+ <a href="/auth/github/authorize" class="inline-flex items-center bg-gradient-to-r from-gray-800 to-black text-white px-6 py-3 rounded-full font-semibold hover:scale-105 transition-transform">
99
  Sign Up with GitHub <i class="bx bxl-github ml-2"></i>
100
  </a>
101
  </div>
 
208
  }
209
  function closeCardDetails(cardId) {
210
  document.getElementById(`${cardId}-details`).classList.add('hidden');
211
+ }
212
+ if ('serviceWorker' in navigator) {
213
+ navigator.serviceWorker.register('/static/js/sw.js')
214
+ .then(function(reg) {
215
+ console.log('✅ Service Worker Registered', reg);
216
+ }).catch(function(err) {
217
+ console.error('❌ Service Worker registration failed', err);
218
+ });
219
  }
220
  </script>
221
  </body>
utils/generation.py CHANGED
@@ -35,14 +35,25 @@ HF_TOKEN = os.getenv("HF_TOKEN")
35
  BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
36
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
37
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
38
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b:together")
39
- SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2:featherless-ai")
40
- TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-20b:together")
41
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
42
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
43
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
44
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
45
 
 
 
 
 
 
 
 
 
 
 
 
46
  def check_model_availability(model_name: str, api_base: str, api_key: str) -> tuple[bool, str]:
47
  try:
48
  response = requests.get(
@@ -64,7 +75,16 @@ def check_model_availability(model_name: str, api_base: str, api_key: str) -> tu
64
  return check_model_availability(model_name, api_base, BACKUP_HF_TOKEN)
65
  return False, api_key
66
 
67
- def select_model(query: str, input_type: str = "text") -> tuple[str, str]:
 
 
 
 
 
 
 
 
 
68
  query_lower = query.lower()
69
  # دعم الصوت
70
  if input_type == "audio" or any(keyword in query_lower for keyword in ["voice", "audio", "speech", "صوت", "تحويل صوت"]):
@@ -106,7 +126,7 @@ def request_generation(
106
  model_name: str,
107
  chat_history: Optional[List[dict]] = None,
108
  temperature: float = 0.7,
109
- max_new_tokens: int = 128000,
110
  reasoning_effort: str = "off",
111
  tools: Optional[List[dict]] = None,
112
  tool_choice: Optional[str] = None,
@@ -176,7 +196,7 @@ def request_generation(
176
  torchaudio.save(audio_file, audio[0], sample_rate=22050, format="wav")
177
  audio_file.seek(0)
178
  audio_data = audio_file.read()
179
- yield audio_data # ← تصحيح: استخدام yield مع البيانات مباشرة
180
  cache[cache_key] = [audio_data]
181
  return
182
  except Exception as e:
 
35
  BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
36
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
37
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
38
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b")
39
+ SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2")
40
+ TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-20b")
41
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
42
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
43
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
44
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
45
 
46
+ # Model alias mapping
47
+ MODEL_ALIASES = {
48
+ "advanced": MODEL_NAME,
49
+ "standard": SECONDARY_MODEL_NAME,
50
+ "light": TERTIARY_MODEL_NAME,
51
+ "image_base": CLIP_BASE_MODEL,
52
+ "image_advanced": CLIP_LARGE_MODEL,
53
+ "audio": ASR_MODEL,
54
+ "tts": TTS_MODEL
55
+ }
56
+
57
  def check_model_availability(model_name: str, api_base: str, api_key: str) -> tuple[bool, str]:
58
  try:
59
  response = requests.get(
 
75
  return check_model_availability(model_name, api_base, BACKUP_HF_TOKEN)
76
  return False, api_key
77
 
78
+ def select_model(query: str, input_type: str = "text", preferred_model: Optional[str] = None) -> tuple[str, str]:
79
+ # If user has a preferred model, use it unless the input type requires a specific model
80
+ if preferred_model and preferred_model in MODEL_ALIASES:
81
+ model_name = MODEL_ALIASES[preferred_model]
82
+ api_endpoint = API_ENDPOINT if model_name in [MODEL_NAME, TERTIARY_MODEL_NAME] else FALLBACK_API_ENDPOINT
83
+ is_available, _ = check_model_availability(model_name, api_endpoint, HF_TOKEN)
84
+ if is_available:
85
+ logger.info(f"Selected preferred model {model_name} with endpoint {api_endpoint} for query: {query}")
86
+ return model_name, api_endpoint
87
+
88
  query_lower = query.lower()
89
  # دعم الصوت
90
  if input_type == "audio" or any(keyword in query_lower for keyword in ["voice", "audio", "speech", "صوت", "تحويل صوت"]):
 
126
  model_name: str,
127
  chat_history: Optional[List[dict]] = None,
128
  temperature: float = 0.7,
129
+ max_new_tokens: int = 2048,
130
  reasoning_effort: str = "off",
131
  tools: Optional[List[dict]] = None,
132
  tool_choice: Optional[str] = None,
 
196
  torchaudio.save(audio_file, audio[0], sample_rate=22050, format="wav")
197
  audio_file.seek(0)
198
  audio_data = audio_file.read()
199
+ yield audio_data
200
  cache[cache_key] = [audio_data]
201
  return
202
  except Exception as e: