Mark-Lasfar
commited on
Commit
·
4c0bb97
1
Parent(s):
a7b1069
Update backend and server frontend for OAuth JSON response, client-side navigation, and add .gitignore
Browse files- api/auth.py +25 -50
- static/js/chat.js +61 -53
api/auth.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
from fastapi_users import FastAPIUsers
|
| 2 |
-
from fastapi_users.authentication import
|
| 3 |
from httpx_oauth.clients.google import GoogleOAuth2
|
| 4 |
from httpx_oauth.clients.github import GitHubOAuth2
|
| 5 |
from fastapi_users.manager import BaseUserManager, IntegerIDMixin
|
| 6 |
-
from fastapi import Depends, Request,
|
| 7 |
from fastapi.responses import JSONResponse, RedirectResponse
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
from sqlalchemy import select
|
|
@@ -22,7 +22,8 @@ from api.models import UserRead, UserCreate, UserUpdate
|
|
| 22 |
# إعداد اللوقينج
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
|
| 25 |
-
|
|
|
|
| 26 |
|
| 27 |
SECRET = os.getenv("JWT_SECRET")
|
| 28 |
if not SECRET or len(SECRET) < 32:
|
|
@@ -34,7 +35,7 @@ def get_jwt_strategy() -> JWTStrategy:
|
|
| 34 |
|
| 35 |
auth_backend = AuthenticationBackend(
|
| 36 |
name="jwt",
|
| 37 |
-
transport=
|
| 38 |
get_strategy=get_jwt_strategy,
|
| 39 |
)
|
| 40 |
|
|
@@ -44,7 +45,6 @@ GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
|
|
| 44 |
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
|
| 45 |
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
|
| 46 |
|
| 47 |
-
# تحقق من توافر بيانات الاعتماد
|
| 48 |
if not all([GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET]):
|
| 49 |
logger.error("One or more OAuth environment variables are missing.")
|
| 50 |
raise ValueError("All OAuth credentials are required.")
|
|
@@ -141,7 +141,6 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
|
|
| 141 |
await self.on_after_login(user, request)
|
| 142 |
return user
|
| 143 |
|
| 144 |
-
# استدعاء user manager من get_user_db
|
| 145 |
async def get_user_manager(user_db: CustomSQLAlchemyUserDatabase = Depends(get_user_db)):
|
| 146 |
yield UserManager(user_db)
|
| 147 |
|
|
@@ -152,7 +151,6 @@ fastapi_users = FastAPIUsers[User, int](
|
|
| 152 |
|
| 153 |
current_active_user = fastapi_users.current_user(active=True, optional=True)
|
| 154 |
|
| 155 |
-
# دالة مساعدة لتوليد JWT token يدويًا
|
| 156 |
async def generate_jwt_token(user: User, secret: str, lifetime_seconds: int) -> str:
|
| 157 |
payload = {
|
| 158 |
"sub": str(user.id),
|
|
@@ -161,8 +159,6 @@ async def generate_jwt_token(user: User, secret: str, lifetime_seconds: int) ->
|
|
| 161 |
}
|
| 162 |
return jwt.encode(payload, secret, algorithm="HS256")
|
| 163 |
|
| 164 |
-
# --- إضافة custom authorize endpoints (يرجع JSON مع authorization_url) ---
|
| 165 |
-
|
| 166 |
async def custom_google_authorize(
|
| 167 |
state: Optional[str] = None,
|
| 168 |
oauth_client=Depends(lambda: google_oauth_client),
|
|
@@ -191,8 +187,6 @@ async def custom_github_authorize(
|
|
| 191 |
"authorization_url": authorization_url
|
| 192 |
}, status_code=200)
|
| 193 |
|
| 194 |
-
# --- Custom Callback endpoints ---
|
| 195 |
-
|
| 196 |
async def custom_oauth_callback(
|
| 197 |
code: str,
|
| 198 |
state: Optional[str] = None,
|
|
@@ -204,15 +198,12 @@ async def custom_oauth_callback(
|
|
| 204 |
):
|
| 205 |
logger.debug(f"Processing Google callback with code: {code}, state: {state}")
|
| 206 |
try:
|
| 207 |
-
# تحقق من الـ state لو موجود (اختياري لـ CSRF protection)
|
| 208 |
if state:
|
| 209 |
logger.debug(f"Received state: {state}")
|
| 210 |
|
| 211 |
-
# Get access token
|
| 212 |
token_data = await oauth_client.get_access_token(code, redirect_url)
|
| 213 |
access_token = token_data["access_token"]
|
| 214 |
|
| 215 |
-
# Manually fetch user info from Google API
|
| 216 |
async with httpx.AsyncClient() as client:
|
| 217 |
user_info_response = await client.get(
|
| 218 |
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
@@ -234,30 +225,25 @@ async def custom_oauth_callback(
|
|
| 234 |
is_verified_by_default=True,
|
| 235 |
)
|
| 236 |
|
| 237 |
-
# توليد الـ JWT token يدويًا
|
| 238 |
token = await generate_jwt_token(user, SECRET, 3600)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
-
# ضبط الـ cookie يدويًا
|
| 241 |
-
response.set_cookie(
|
| 242 |
-
key="fastapiusersauth",
|
| 243 |
-
value=token,
|
| 244 |
-
max_age=3600,
|
| 245 |
-
httponly=True,
|
| 246 |
-
samesite="lax",
|
| 247 |
-
secure=True,
|
| 248 |
-
)
|
| 249 |
-
|
| 250 |
-
# تحقق إذا كان الطلب من التطبيق (Capacitor) أو الويب
|
| 251 |
is_app = request.headers.get("X-Capacitor-App", False)
|
| 252 |
-
|
| 253 |
if is_app:
|
| 254 |
-
# للتطبيق: إرجاع JSON مع الـ token
|
| 255 |
return JSONResponse(content={
|
| 256 |
"message": "Google login successful",
|
| 257 |
"access_token": token
|
| 258 |
}, status_code=200)
|
| 259 |
else:
|
| 260 |
-
# للويب: إرجاع redirect إلى /chat مع الـ token كـ query parameter
|
| 261 |
return RedirectResponse(url=f"/chat?access_token={token}", status_code=303)
|
| 262 |
|
| 263 |
except Exception as e:
|
|
@@ -275,15 +261,12 @@ async def custom_github_oauth_callback(
|
|
| 275 |
):
|
| 276 |
logger.debug(f"Processing GitHub callback with code: {code}, state: {state}")
|
| 277 |
try:
|
| 278 |
-
# تحقق من الـ state لو موجود (اختياري لـ CSRF protection)
|
| 279 |
if state:
|
| 280 |
logger.debug(f"Received state: {state}")
|
| 281 |
|
| 282 |
-
# Get access token
|
| 283 |
token_data = await oauth_client.get_access_token(code, redirect_url)
|
| 284 |
access_token = token_data["access_token"]
|
| 285 |
|
| 286 |
-
# Manually fetch user info from GitHub API
|
| 287 |
async with httpx.AsyncClient() as client:
|
| 288 |
user_info_response = await client.get(
|
| 289 |
"https://api.github.com/user",
|
|
@@ -296,7 +279,6 @@ async def custom_github_oauth_callback(
|
|
| 296 |
raise ValueError(f"Failed to fetch user info: {user_info_response.text}")
|
| 297 |
user_info = user_info_response.json()
|
| 298 |
|
| 299 |
-
# Get email if not in user info
|
| 300 |
email = user_info.get("email")
|
| 301 |
if not email:
|
| 302 |
email_response = await client.get(
|
|
@@ -323,37 +305,31 @@ async def custom_github_oauth_callback(
|
|
| 323 |
is_verified_by_default=True,
|
| 324 |
)
|
| 325 |
|
| 326 |
-
# توليد الـ JWT token يدويًا
|
| 327 |
token = await generate_jwt_token(user, SECRET, 3600)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
|
| 329 |
-
# ضبط الـ cookie يدويًا
|
| 330 |
-
response.set_cookie(
|
| 331 |
-
key="fastapiusersauth",
|
| 332 |
-
value=token,
|
| 333 |
-
max_age=3600,
|
| 334 |
-
httponly=True,
|
| 335 |
-
samesite="lax",
|
| 336 |
-
secure=True,
|
| 337 |
-
)
|
| 338 |
-
|
| 339 |
-
# تحقق إذا كان الطلب من التطبيق (Capacitor) أو الويب
|
| 340 |
is_app = request.headers.get("X-Capacitor-App", False)
|
| 341 |
-
|
| 342 |
if is_app:
|
| 343 |
-
# للتطبيق: إرجاع JSON مع الـ token
|
| 344 |
return JSONResponse(content={
|
| 345 |
"message": "GitHub login successful",
|
| 346 |
"access_token": token
|
| 347 |
}, status_code=200)
|
| 348 |
else:
|
| 349 |
-
# للويب: إرجاع redirect إلى /chat مع الـ token كـ query parameter
|
| 350 |
return RedirectResponse(url=f"/chat?access_token={token}", status_code=303)
|
| 351 |
|
| 352 |
except Exception as e:
|
| 353 |
logger.error(f"Error in GitHub OAuth callback: {str(e)}")
|
| 354 |
return JSONResponse(content={"detail": str(e)}, status_code=400)
|
| 355 |
|
| 356 |
-
# تضمين الراوترات داخل التطبيق
|
| 357 |
def get_auth_router(app: FastAPI):
|
| 358 |
app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
|
| 359 |
app.include_router(fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"])
|
|
@@ -361,7 +337,6 @@ def get_auth_router(app: FastAPI):
|
|
| 361 |
app.include_router(fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"])
|
| 362 |
app.include_router(fastapi_users.get_users_router(UserRead, UserUpdate), prefix="/users", tags=["users"])
|
| 363 |
|
| 364 |
-
# إضافة الـ custom OAuth endpoints يدويًا
|
| 365 |
app.get("/auth/google/authorize")(custom_google_authorize)
|
| 366 |
app.get("/auth/google/callback")(custom_oauth_callback)
|
| 367 |
app.get("/auth/github/authorize")(custom_github_authorize)
|
|
|
|
| 1 |
from fastapi_users import FastAPIUsers
|
| 2 |
+
from fastapi_users.authentication import BearerTransport, JWTStrategy, AuthenticationBackend
|
| 3 |
from httpx_oauth.clients.google import GoogleOAuth2
|
| 4 |
from httpx_oauth.clients.github import GitHubOAuth2
|
| 5 |
from fastapi_users.manager import BaseUserManager, IntegerIDMixin
|
| 6 |
+
from fastapi import Depends, Request, Response, FastAPI
|
| 7 |
from fastapi.responses import JSONResponse, RedirectResponse
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
from sqlalchemy import select
|
|
|
|
| 22 |
# إعداد اللوقينج
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
|
| 25 |
+
# استخدام BearerTransport بدل CookieTransport
|
| 26 |
+
bearer_transport = BearerTransport(tokenUrl="/auth/jwt/login")
|
| 27 |
|
| 28 |
SECRET = os.getenv("JWT_SECRET")
|
| 29 |
if not SECRET or len(SECRET) < 32:
|
|
|
|
| 35 |
|
| 36 |
auth_backend = AuthenticationBackend(
|
| 37 |
name="jwt",
|
| 38 |
+
transport=bearer_transport, # تغيير إلى BearerTransport
|
| 39 |
get_strategy=get_jwt_strategy,
|
| 40 |
)
|
| 41 |
|
|
|
|
| 45 |
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
|
| 46 |
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
|
| 47 |
|
|
|
|
| 48 |
if not all([GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET]):
|
| 49 |
logger.error("One or more OAuth environment variables are missing.")
|
| 50 |
raise ValueError("All OAuth credentials are required.")
|
|
|
|
| 141 |
await self.on_after_login(user, request)
|
| 142 |
return user
|
| 143 |
|
|
|
|
| 144 |
async def get_user_manager(user_db: CustomSQLAlchemyUserDatabase = Depends(get_user_db)):
|
| 145 |
yield UserManager(user_db)
|
| 146 |
|
|
|
|
| 151 |
|
| 152 |
current_active_user = fastapi_users.current_user(active=True, optional=True)
|
| 153 |
|
|
|
|
| 154 |
async def generate_jwt_token(user: User, secret: str, lifetime_seconds: int) -> str:
|
| 155 |
payload = {
|
| 156 |
"sub": str(user.id),
|
|
|
|
| 159 |
}
|
| 160 |
return jwt.encode(payload, secret, algorithm="HS256")
|
| 161 |
|
|
|
|
|
|
|
| 162 |
async def custom_google_authorize(
|
| 163 |
state: Optional[str] = None,
|
| 164 |
oauth_client=Depends(lambda: google_oauth_client),
|
|
|
|
| 187 |
"authorization_url": authorization_url
|
| 188 |
}, status_code=200)
|
| 189 |
|
|
|
|
|
|
|
| 190 |
async def custom_oauth_callback(
|
| 191 |
code: str,
|
| 192 |
state: Optional[str] = None,
|
|
|
|
| 198 |
):
|
| 199 |
logger.debug(f"Processing Google callback with code: {code}, state: {state}")
|
| 200 |
try:
|
|
|
|
| 201 |
if state:
|
| 202 |
logger.debug(f"Received state: {state}")
|
| 203 |
|
|
|
|
| 204 |
token_data = await oauth_client.get_access_token(code, redirect_url)
|
| 205 |
access_token = token_data["access_token"]
|
| 206 |
|
|
|
|
| 207 |
async with httpx.AsyncClient() as client:
|
| 208 |
user_info_response = await client.get(
|
| 209 |
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
|
|
| 225 |
is_verified_by_default=True,
|
| 226 |
)
|
| 227 |
|
|
|
|
| 228 |
token = await generate_jwt_token(user, SECRET, 3600)
|
| 229 |
+
|
| 230 |
+
# ما نضبطش cookie لأننا بنستخدم Bearer token
|
| 231 |
+
# response.set_cookie(
|
| 232 |
+
# key="fastapiusersauth",
|
| 233 |
+
# value=token,
|
| 234 |
+
# max_age=3600,
|
| 235 |
+
# httponly=True,
|
| 236 |
+
# samesite="lax",
|
| 237 |
+
# secure=True,
|
| 238 |
+
# )
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
is_app = request.headers.get("X-Capacitor-App", False)
|
|
|
|
| 241 |
if is_app:
|
|
|
|
| 242 |
return JSONResponse(content={
|
| 243 |
"message": "Google login successful",
|
| 244 |
"access_token": token
|
| 245 |
}, status_code=200)
|
| 246 |
else:
|
|
|
|
| 247 |
return RedirectResponse(url=f"/chat?access_token={token}", status_code=303)
|
| 248 |
|
| 249 |
except Exception as e:
|
|
|
|
| 261 |
):
|
| 262 |
logger.debug(f"Processing GitHub callback with code: {code}, state: {state}")
|
| 263 |
try:
|
|
|
|
| 264 |
if state:
|
| 265 |
logger.debug(f"Received state: {state}")
|
| 266 |
|
|
|
|
| 267 |
token_data = await oauth_client.get_access_token(code, redirect_url)
|
| 268 |
access_token = token_data["access_token"]
|
| 269 |
|
|
|
|
| 270 |
async with httpx.AsyncClient() as client:
|
| 271 |
user_info_response = await client.get(
|
| 272 |
"https://api.github.com/user",
|
|
|
|
| 279 |
raise ValueError(f"Failed to fetch user info: {user_info_response.text}")
|
| 280 |
user_info = user_info_response.json()
|
| 281 |
|
|
|
|
| 282 |
email = user_info.get("email")
|
| 283 |
if not email:
|
| 284 |
email_response = await client.get(
|
|
|
|
| 305 |
is_verified_by_default=True,
|
| 306 |
)
|
| 307 |
|
|
|
|
| 308 |
token = await generate_jwt_token(user, SECRET, 3600)
|
| 309 |
+
|
| 310 |
+
# ما نضبطش cookie لأننا بنستخدم Bearer token
|
| 311 |
+
# response.set_cookie(
|
| 312 |
+
# key="fastapiusersauth",
|
| 313 |
+
# value=token,
|
| 314 |
+
# max_age=3600,
|
| 315 |
+
# httponly=True,
|
| 316 |
+
# samesite="lax",
|
| 317 |
+
# secure=True,
|
| 318 |
+
# )
|
| 319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
is_app = request.headers.get("X-Capacitor-App", False)
|
|
|
|
| 321 |
if is_app:
|
|
|
|
| 322 |
return JSONResponse(content={
|
| 323 |
"message": "GitHub login successful",
|
| 324 |
"access_token": token
|
| 325 |
}, status_code=200)
|
| 326 |
else:
|
|
|
|
| 327 |
return RedirectResponse(url=f"/chat?access_token={token}", status_code=303)
|
| 328 |
|
| 329 |
except Exception as e:
|
| 330 |
logger.error(f"Error in GitHub OAuth callback: {str(e)}")
|
| 331 |
return JSONResponse(content={"detail": str(e)}, status_code=400)
|
| 332 |
|
|
|
|
| 333 |
def get_auth_router(app: FastAPI):
|
| 334 |
app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
|
| 335 |
app.include_router(fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"])
|
|
|
|
| 337 |
app.include_router(fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"])
|
| 338 |
app.include_router(fastapi_users.get_users_router(UserRead, UserUpdate), prefix="/users", tags=["users"])
|
| 339 |
|
|
|
|
| 340 |
app.get("/auth/google/authorize")(custom_google_authorize)
|
| 341 |
app.get("/auth/google/callback")(custom_oauth_callback)
|
| 342 |
app.get("/auth/github/authorize")(custom_github_authorize)
|
static/js/chat.js
CHANGED
|
@@ -54,20 +54,15 @@ let abortController = null;
|
|
| 54 |
|
| 55 |
|
| 56 |
async function checkAuth() {
|
| 57 |
-
// تحقق من وجود access_token في query parameters
|
| 58 |
const urlParams = new URLSearchParams(window.location.search);
|
| 59 |
const accessTokenFromUrl = urlParams.get('access_token');
|
| 60 |
if (accessTokenFromUrl) {
|
| 61 |
console.log('Access token found in URL, saving to localStorage');
|
| 62 |
localStorage.setItem('token', accessTokenFromUrl);
|
| 63 |
-
// إزالة access_token من الـ URL عشان الأمان
|
| 64 |
window.history.replaceState({}, document.title, '/chat');
|
| 65 |
}
|
| 66 |
|
| 67 |
-
// تحقق من وجود token في localStorage
|
| 68 |
let token = localStorage.getItem('token');
|
| 69 |
-
|
| 70 |
-
// لو مفيش token في localStorage، حاول استخرج الـ token من الـ cookie
|
| 71 |
if (!token && typeof Cookies !== 'undefined') {
|
| 72 |
token = Cookies.get('fastapiusersauth');
|
| 73 |
if (token) {
|
|
@@ -111,6 +106,7 @@ async function checkAuth() {
|
|
| 111 |
}
|
| 112 |
}
|
| 113 |
|
|
|
|
| 114 |
async function handleSession() {
|
| 115 |
const sessionId = sessionStorage.getItem('session_id');
|
| 116 |
if (!sessionId) {
|
|
@@ -123,67 +119,79 @@ async function handleSession() {
|
|
| 123 |
return sessionId;
|
| 124 |
}
|
| 125 |
|
|
|
|
| 126 |
window.addEventListener('load', async () => {
|
| 127 |
console.log('Chat page loaded, checking authentication');
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
| 134 |
|
| 135 |
-
|
| 136 |
-
enterChatView(true);
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
if (userInfoElement) {
|
| 152 |
-
userInfoElement.textContent = 'Anonymous';
|
| 153 |
-
}
|
| 154 |
-
await handleSession();
|
| 155 |
-
if (conversationHistory.length > 0) {
|
| 156 |
-
console.log('Restoring conversation history from sessionStorage:', conversationHistory);
|
| 157 |
-
conversationHistory.forEach(msg => {
|
| 158 |
-
console.log('Adding message from history:', msg);
|
| 159 |
-
addMsg(msg.role, msg.content);
|
| 160 |
-
});
|
| 161 |
} else {
|
| 162 |
-
console.log('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
}
|
| 164 |
-
}
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
}
|
| 173 |
-
setupTouchGestures();
|
| 174 |
});
|
| 175 |
-
|
| 176 |
// Update send button state
|
| 177 |
function updateSendButtonState() {
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
|
|
|
|
|
|
| 185 |
}
|
| 186 |
|
|
|
|
| 187 |
// Render markdown content with RTL support
|
| 188 |
function renderMarkdown(el) {
|
| 189 |
const raw = el.dataset.text || '';
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
async function checkAuth() {
|
|
|
|
| 57 |
const urlParams = new URLSearchParams(window.location.search);
|
| 58 |
const accessTokenFromUrl = urlParams.get('access_token');
|
| 59 |
if (accessTokenFromUrl) {
|
| 60 |
console.log('Access token found in URL, saving to localStorage');
|
| 61 |
localStorage.setItem('token', accessTokenFromUrl);
|
|
|
|
| 62 |
window.history.replaceState({}, document.title, '/chat');
|
| 63 |
}
|
| 64 |
|
|
|
|
| 65 |
let token = localStorage.getItem('token');
|
|
|
|
|
|
|
| 66 |
if (!token && typeof Cookies !== 'undefined') {
|
| 67 |
token = Cookies.get('fastapiusersauth');
|
| 68 |
if (token) {
|
|
|
|
| 106 |
}
|
| 107 |
}
|
| 108 |
|
| 109 |
+
|
| 110 |
async function handleSession() {
|
| 111 |
const sessionId = sessionStorage.getItem('session_id');
|
| 112 |
if (!sessionId) {
|
|
|
|
| 119 |
return sessionId;
|
| 120 |
}
|
| 121 |
|
| 122 |
+
|
| 123 |
window.addEventListener('load', async () => {
|
| 124 |
console.log('Chat page loaded, checking authentication');
|
| 125 |
+
try {
|
| 126 |
+
AOS.init({
|
| 127 |
+
duration: 800,
|
| 128 |
+
easing: 'ease-out-cubic',
|
| 129 |
+
once: true,
|
| 130 |
+
offset: 50,
|
| 131 |
+
});
|
| 132 |
|
| 133 |
+
enterChatView(true);
|
|
|
|
| 134 |
|
| 135 |
+
const authResult = await checkAuth();
|
| 136 |
+
const userInfoElement = document.getElementById('user-info');
|
| 137 |
+
if (authResult.authenticated) {
|
| 138 |
+
console.log('User authenticated:', authResult.user);
|
| 139 |
+
if (userInfoElement) {
|
| 140 |
+
userInfoElement.textContent = `Welcome, ${authResult.user.email}`;
|
| 141 |
+
} else {
|
| 142 |
+
console.warn('user-info element not found');
|
| 143 |
+
}
|
| 144 |
+
if (typeof currentConversationId !== 'undefined' && currentConversationId) {
|
| 145 |
+
console.log('Authenticated user, loading conversation with ID:', currentConversationId);
|
| 146 |
+
await loadConversation(currentConversationId);
|
| 147 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
} else {
|
| 149 |
+
console.log('User not authenticated, handling as anonymous');
|
| 150 |
+
if (userInfoElement) {
|
| 151 |
+
userInfoElement.textContent = 'Anonymous';
|
| 152 |
+
} else {
|
| 153 |
+
console.warn('user-info element not found');
|
| 154 |
+
}
|
| 155 |
+
await handleSession();
|
| 156 |
+
if (typeof conversationHistory !== 'undefined' && conversationHistory.length > 0) {
|
| 157 |
+
console.log('Restoring conversation history from sessionStorage:', conversationHistory);
|
| 158 |
+
conversationHistory.forEach(msg => {
|
| 159 |
+
console.log('Adding message from history:', msg);
|
| 160 |
+
addMsg(msg.role, msg.content);
|
| 161 |
+
});
|
| 162 |
+
} else {
|
| 163 |
+
console.log('No conversation history, starting fresh');
|
| 164 |
+
}
|
| 165 |
}
|
|
|
|
| 166 |
|
| 167 |
+
autoResizeTextarea();
|
| 168 |
+
updateSendButtonState();
|
| 169 |
+
if (uiElements.swipeHint) {
|
| 170 |
+
setTimeout(() => {
|
| 171 |
+
uiElements.swipeHint.style.display = 'none';
|
| 172 |
+
}, 3000);
|
| 173 |
+
} else {
|
| 174 |
+
console.warn('swipeHint element not found');
|
| 175 |
+
}
|
| 176 |
+
setupTouchGestures();
|
| 177 |
+
} catch (error) {
|
| 178 |
+
console.error('Error in window.load handler:', error);
|
| 179 |
}
|
|
|
|
| 180 |
});
|
|
|
|
| 181 |
// Update send button state
|
| 182 |
function updateSendButtonState() {
|
| 183 |
+
if (uiElements.sendBtn && uiElements.input && uiElements.fileInput && uiElements.audioInput) {
|
| 184 |
+
const hasInput = uiElements.input.value.trim() !== '' ||
|
| 185 |
+
uiElements.fileInput.files.length > 0 ||
|
| 186 |
+
uiElements.audioInput.files.length > 0;
|
| 187 |
+
uiElements.sendBtn.disabled = !hasInput || isRequestActive || isRecording;
|
| 188 |
+
console.log('Send button state updated:', { hasInput, isRequestActive, isRecording, disabled: uiElements.sendBtn.disabled });
|
| 189 |
+
} else {
|
| 190 |
+
console.warn('One or more uiElements are missing:', uiElements);
|
| 191 |
+
}
|
| 192 |
}
|
| 193 |
|
| 194 |
+
|
| 195 |
// Render markdown content with RTL support
|
| 196 |
function renderMarkdown(el) {
|
| 197 |
const raw = el.dataset.text || '';
|