Commit
·
87b09dd
1
Parent(s):
6ac883d
Full Update
Browse files- api/auth.py +2 -8
- api/endpoints.py +437 -162
- api/models.py +84 -4
- main.py +133 -50
- static/css/sidebar.css +151 -0
- static/images/generate_icons.py +14 -0
- static/images/ic/404.html +31 -0
- static/images/ic/500.html +32 -0
- static/images/ic/about.html +279 -0
- static/images/ic/blog.html +237 -0
- static/images/ic/blog_post.html +263 -0
- static/images/ic/chat.html +294 -0
- static/images/ic/docs.html +284 -0
- static/images/ic/index.html +342 -0
- static/images/ic/login.html +218 -0
- static/images/ic/mg-128.png +0 -0
- static/images/ic/mg-192.png +0 -0
- static/images/ic/mg-256.png +0 -0
- static/images/ic/mg-384.png +0 -0
- static/images/ic/mg-48.png +0 -0
- static/images/ic/mg-512.png +0 -0
- static/images/ic/mg-72.png +0 -0
- static/images/ic/mg-96.png +0 -0
- static/images/ic/mg.svg +1 -0
- static/images/ic/register.html +214 -0
- static/images/icons/mg-128.png +0 -0
- static/images/icons/mg-192.png +0 -0
- static/images/icons/mg-256.png +0 -0
- static/images/icons/mg-384.png +0 -0
- static/images/icons/mg-48.png +0 -0
- static/images/icons/mg-512.png +0 -0
- static/images/icons/mg-72.png +0 -0
- static/images/icons/mg-96.png +0 -0
- static/images/mg.png +0 -0
- static/images/mg.svg +110 -1
- static/images/mgz.svg +1 -0
- static/images/swipe-hint.svg +8 -0
- static/js/chat.js +694 -447
- static/js/sw.js +44 -0
- static/manifest.json +57 -0
- templates/500.html +13 -0
- templates/blog_post.html +2 -0
- templates/chat.html +314 -151
- templates/index.html +47 -1
- templates/login.html +50 -3
- templates/register.html +10 -2
- 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 |
-
|
| 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 |
-
|
| 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
|
| 22 |
-
SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2
|
| 23 |
-
TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-20b
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 68 |
-
|
| 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 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
detail="Message limit reached. Please log in to continue."
|
| 87 |
)
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
stream = request_generation(
|
| 91 |
api_key=HF_TOKEN,
|
| 92 |
api_base=api_endpoint,
|
| 93 |
message=req.message,
|
| 94 |
-
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 117 |
-
|
| 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 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 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=
|
| 148 |
input_type="audio",
|
| 149 |
audio_data=audio_data,
|
| 150 |
output_format="text"
|
| 151 |
)
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 170 |
-
|
| 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=
|
| 194 |
input_type="tts",
|
| 195 |
output_format="audio"
|
| 196 |
)
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 215 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
stream = request_generation(
|
| 236 |
api_key=HF_TOKEN,
|
| 237 |
api_base=api_endpoint,
|
| 238 |
message=prompt,
|
| 239 |
-
system_prompt=
|
| 240 |
model_name=model_name,
|
| 241 |
temperature=0.7,
|
| 242 |
-
max_new_tokens=
|
| 243 |
input_type="text",
|
| 244 |
output_format=output_format
|
| 245 |
)
|
| 246 |
if output_format == "audio":
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 267 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
stream = request_generation(
|
| 285 |
api_key=HF_TOKEN,
|
| 286 |
api_base=api_endpoint,
|
| 287 |
message=message,
|
| 288 |
-
system_prompt=
|
| 289 |
model_name=model_name,
|
| 290 |
temperature=0.7,
|
| 291 |
-
max_new_tokens=
|
| 292 |
input_type="text",
|
| 293 |
output_format=output_format
|
| 294 |
)
|
| 295 |
if output_format == "audio":
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
detail="Message limit reached. Please log in to continue."
|
| 329 |
)
|
| 330 |
-
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 338 |
model_name=model_name,
|
| 339 |
temperature=0.7,
|
| 340 |
-
max_new_tokens=
|
| 341 |
input_type="image",
|
| 342 |
image_data=image_data,
|
| 343 |
output_format=output_format
|
| 344 |
)
|
| 345 |
if output_format == "audio":
|
| 346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 23 |
is_active = Column(Boolean, default=True)
|
| 24 |
is_superuser = Column(Boolean, default=False)
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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] =
|
| 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 |
-
#
|
| 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 |
-
|
|
|
|
| 42 |
|
| 43 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 57 |
QUEUE_SIZE = int(os.getenv("QUEUE_SIZE", 80))
|
| 58 |
CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
|
| 59 |
|
| 60 |
-
#
|
| 61 |
app = FastAPI(title="MGZon Chatbot API")
|
| 62 |
|
| 63 |
-
#
|
| 64 |
-
app.add_middleware(SessionMiddleware, secret_key=
|
| 65 |
|
| 66 |
-
#
|
| 67 |
Base.metadata.create_all(bind=engine)
|
| 68 |
|
| 69 |
-
#
|
|
|
|
| 70 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 71 |
|
| 72 |
-
#
|
| 73 |
app.add_middleware(
|
| 74 |
CORSMiddleware,
|
| 75 |
-
allow_origins=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
allow_credentials=True,
|
| 77 |
allow_methods=["*"],
|
| 78 |
allow_headers=["*"],
|
|
|
|
|
|
|
| 79 |
)
|
| 80 |
|
| 81 |
-
#
|
| 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,
|
| 99 |
prefix="/auth/google",
|
| 100 |
tags=["auth"],
|
| 101 |
)
|
| 102 |
app.include_router(
|
| 103 |
-
fastapi_users.get_oauth_router(github_oauth_client, auth_backend,
|
| 104 |
prefix="/auth/github",
|
| 105 |
tags=["auth"],
|
| 106 |
)
|
|
|
|
| 107 |
|
| 108 |
-
#
|
| 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(
|
| 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(
|
| 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(
|
| 138 |
if user:
|
| 139 |
return RedirectResponse(url="/chat", status_code=302)
|
| 140 |
return templates.TemplateResponse("register.html", {"request": request})
|
| 141 |
|
| 142 |
-
# Chat
|
| 143 |
@app.get("/chat", response_class=HTMLResponse)
|
| 144 |
-
async def chat(request: Request, user: User = Depends(
|
| 145 |
return templates.TemplateResponse("chat.html", {"request": request, "user": user})
|
| 146 |
|
| 147 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
@app.get("/about", response_class=HTMLResponse)
|
| 149 |
-
async def about(request: Request, user: User = Depends(
|
| 150 |
return templates.TemplateResponse("about.html", {"request": request, "user": user})
|
| 151 |
|
| 152 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 156 |
return templates.TemplateResponse("blog.html", {"request": request, "posts": posts})
|
| 157 |
|
| 158 |
-
#
|
| 159 |
@app.get("/blog/{post_id}", response_class=HTMLResponse)
|
| 160 |
async def blog_post(request: Request, post_id: str):
|
| 161 |
-
post = await
|
| 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
|
| 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
|
| 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
|
| 177 |
@app.get("/sitemap.xml", response_class=PlainTextResponse)
|
| 178 |
async def sitemap():
|
| 179 |
-
posts = await
|
|
|
|
| 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>
|
| 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>
|
| 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>
|
| 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>
|
| 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>
|
| 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>
|
| 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>
|
| 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
|
| 235 |
@app.get("/gradio", response_class=RedirectResponse)
|
| 236 |
async def launch_chatbot():
|
| 237 |
return RedirectResponse(url="/chat", status_code=302)
|
| 238 |
|
| 239 |
-
#
|
| 240 |
-
app.
|
|
|
|
|
|
|
| 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">×</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">×</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">×</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
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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`;
|
| 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 (
|
|
|
|
|
|
|
| 57 |
enterChatView();
|
| 58 |
-
conversationHistory.forEach(msg =>
|
| 59 |
-
|
| 60 |
-
|
|
|
|
| 61 |
}
|
| 62 |
-
// Initialize textarea height
|
| 63 |
autoResizeTextarea();
|
| 64 |
-
|
| 65 |
-
if (
|
| 66 |
-
|
|
|
|
|
|
|
| 67 |
}
|
|
|
|
| 68 |
});
|
| 69 |
|
| 70 |
-
//
|
| 71 |
function checkAuth() {
|
| 72 |
return localStorage.getItem('token');
|
| 73 |
}
|
| 74 |
|
| 75 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 86 |
const wrapper = el.querySelector('.md-content');
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 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 |
-
//
|
| 111 |
function enterChatView() {
|
| 112 |
-
if (
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
}
|
| 119 |
-
if (chatBox) {
|
| 120 |
-
chatBox.style.display = 'flex';
|
| 121 |
-
}
|
| 122 |
-
if (initialContent) {
|
| 123 |
-
initialContent.style.display = 'none';
|
| 124 |
}
|
|
|
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
-
//
|
| 128 |
function leaveChatView() {
|
| 129 |
-
if (
|
| 130 |
-
|
| 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 |
-
//
|
| 145 |
function addMsg(who, text) {
|
| 146 |
const div = document.createElement('div');
|
| 147 |
-
div.className =
|
| 148 |
div.dataset.text = text;
|
| 149 |
renderMarkdown(div);
|
| 150 |
-
if (chatBox) {
|
| 151 |
-
chatBox.appendChild(div);
|
| 152 |
-
chatBox.
|
| 153 |
-
chatBox.scrollTop = chatBox.scrollHeight;
|
| 154 |
}
|
| 155 |
return div;
|
| 156 |
}
|
| 157 |
|
| 158 |
-
// Clear all
|
| 159 |
function clearAllMessages() {
|
| 160 |
stopStream(true);
|
| 161 |
conversationHistory = [];
|
| 162 |
sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
|
| 163 |
currentAssistantText = '';
|
| 164 |
if (streamMsg) {
|
| 165 |
-
|
| 166 |
-
if (loadingEl) loadingEl.remove();
|
| 167 |
streamMsg = null;
|
| 168 |
}
|
| 169 |
-
if (chatBox)
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
if (
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
if (
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 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
|
| 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
|
| 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 (
|
| 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 (
|
| 258 |
-
btn.classList.remove('recording');
|
| 259 |
-
}
|
| 260 |
});
|
| 261 |
}
|
| 262 |
|
| 263 |
function stopVoiceRecording() {
|
| 264 |
-
if (mediaRecorder
|
| 265 |
mediaRecorder.stop();
|
| 266 |
-
if (
|
| 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 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
}
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
}
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
}
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
}
|
|
|
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
|
|
|
|
|
|
|
| 311 |
try {
|
| 312 |
-
const
|
| 313 |
-
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
| 314 |
-
const response = await fetch('/api/audio-transcription', {
|
| 315 |
method: 'POST',
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
|
|
|
|
|
|
| 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('
|
| 353 |
}
|
| 354 |
-
|
| 355 |
const data = await response.json();
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 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 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
}
|
| 371 |
-
|
| 372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
}
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
streamMsg = null;
|
| 382 |
-
isRequestActive = false;
|
| 383 |
-
abortController = null;
|
| 384 |
}
|
| 385 |
-
|
| 386 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
}
|
| 388 |
-
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
}
|
| 391 |
-
}
|
| 392 |
}
|
| 393 |
-
|
|
|
|
| 394 |
async function submitMessage() {
|
| 395 |
if (isRequestActive || isRecording) return;
|
| 396 |
-
let message = input
|
| 397 |
-
let
|
|
|
|
| 398 |
let endpoint = '/api/chat';
|
|
|
|
| 399 |
let inputType = 'text';
|
| 400 |
let outputFormat = 'text';
|
|
|
|
| 401 |
|
| 402 |
-
if (fileInput
|
| 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
|
| 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 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 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 |
-
|
| 476 |
-
|
| 477 |
-
|
| 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 (
|
| 488 |
-
|
| 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(
|
| 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
|
| 528 |
-
|
| 529 |
-
|
| 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 =
|
| 537 |
-
currentAssistantText = buffer;
|
| 538 |
-
const loadingEl = streamMsg.querySelector('.loading');
|
| 539 |
-
if (loadingEl) loadingEl.remove();
|
| 540 |
renderMarkdown(streamMsg);
|
| 541 |
-
|
| 542 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 602 |
-
|
| 603 |
-
if (btn) {
|
| 604 |
-
btn.style.display = 'inline-flex';
|
| 605 |
-
}
|
| 606 |
-
if (stopBtn) {
|
| 607 |
-
stopBtn.style.pointerEvents = 'auto';
|
| 608 |
-
}
|
| 609 |
}
|
| 610 |
|
| 611 |
-
//
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
}
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
});
|
| 624 |
-
}
|
| 625 |
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
if (fileInput) {
|
| 630 |
-
fileInput.click();
|
| 631 |
-
}
|
| 632 |
});
|
| 633 |
}
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
});
|
| 640 |
}
|
| 641 |
-
if (fileInput) {
|
| 642 |
-
fileInput.addEventListener('change', previewFile);
|
| 643 |
-
}
|
| 644 |
-
if (audioInput) {
|
| 645 |
-
audioInput.addEventListener('change', previewFile);
|
| 646 |
-
}
|
| 647 |
|
| 648 |
-
//
|
| 649 |
-
if (
|
| 650 |
-
|
| 651 |
-
if (
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
|
|
|
|
|
|
|
|
|
| 657 |
}
|
| 658 |
});
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
});
|
| 664 |
-
|
|
|
|
|
|
|
| 665 |
e.preventDefault();
|
| 666 |
-
if (
|
| 667 |
startVoiceRecording();
|
| 668 |
});
|
| 669 |
-
|
| 670 |
e.preventDefault();
|
| 671 |
-
if (isRecording)
|
| 672 |
-
stopVoiceRecording();
|
| 673 |
-
}
|
| 674 |
});
|
| 675 |
-
|
| 676 |
e.preventDefault();
|
| 677 |
-
if (isRecording)
|
| 678 |
-
stopVoiceRecording();
|
| 679 |
-
}
|
| 680 |
});
|
| 681 |
}
|
| 682 |
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
form.addEventListener('submit', e => {
|
| 686 |
e.preventDefault();
|
| 687 |
-
if (!isRecording)
|
| 688 |
-
submitMessage();
|
| 689 |
-
}
|
| 690 |
});
|
| 691 |
}
|
| 692 |
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
input.addEventListener('keydown', e => {
|
| 696 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 697 |
e.preventDefault();
|
| 698 |
-
if (!isRecording && !
|
| 699 |
-
submitMessage();
|
| 700 |
-
}
|
| 701 |
}
|
| 702 |
});
|
| 703 |
-
|
| 704 |
-
// Auto-resize textarea on input.
|
| 705 |
-
input.addEventListener('input', () => {
|
| 706 |
autoResizeTextarea();
|
| 707 |
-
|
| 708 |
-
btn.disabled = input.value.trim() === '' && fileInput.files.length === 0 && audioInput.files.length === 0;
|
| 709 |
-
}
|
| 710 |
});
|
| 711 |
}
|
| 712 |
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
stopBtn.style.pointerEvents = 'none';
|
| 717 |
stopStream();
|
| 718 |
});
|
| 719 |
}
|
| 720 |
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
});
|
| 726 |
}
|
| 727 |
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
clearBtn.addEventListener('click', () => {
|
| 731 |
-
clearAllMessages();
|
| 732 |
-
});
|
| 733 |
}
|
| 734 |
|
| 735 |
-
|
| 736 |
-
|
| 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 |
-
<!--
|
| 80 |
-
<
|
| 81 |
-
<div class="
|
| 82 |
-
<
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
<
|
| 87 |
-
<svg
|
| 88 |
-
<path d="
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
<!-- Main Content -->
|
| 115 |
-
<
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
| 119 |
</div>
|
| 120 |
-
<
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 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 |
-
|
| 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 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
| 168 |
</svg>
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
<
|
| 172 |
-
<svg
|
| 173 |
-
<path d="
|
| 174 |
-
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
| 175 |
-
<path d="M12 19v4" />
|
| 176 |
</svg>
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
<
|
| 180 |
-
<svg
|
| 181 |
-
<path
|
| 182 |
</svg>
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
| 187 |
</svg>
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
</div>
|
| 190 |
</div>
|
| 191 |
-
<div id="
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
</div>
|
| 213 |
</div>
|
| 214 |
-
<div class="
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
</div>
|
| 223 |
-
<
|
| 224 |
-
<
|
| 225 |
-
|
| 226 |
-
|
| 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 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
</div>
|
| 238 |
-
<p class="mt-4 text-xs">© 2025 Mark Al-Asfar & MGZon AI. All rights reserved.</p>
|
| 239 |
</div>
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
<!-- Scripts -->
|
|
|
|
| 243 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
}
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/
|
| 100 |
Login with Google <i class="bx bxl-google ml-2"></i>
|
| 101 |
</a>
|
| 102 |
-
<a href="/auth/github/
|
| 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/
|
| 96 |
Sign Up with Google <i class="bx bxl-google ml-2"></i>
|
| 97 |
</a>
|
| 98 |
-
<a href="/auth/github/
|
| 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
|
| 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 |
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 =
|
| 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
|
| 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:
|