Vishwanath77 commited on
Commit
b82f276
·
verified ·
1 Parent(s): 914f0ee

Upload 78 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +6 -0
  2. Dockerfile +32 -0
  3. requirements.txt +25 -0
  4. src/apps/.env +26 -0
  5. src/apps/__pycache__/app.cpython-312.pyc +0 -0
  6. src/apps/__pycache__/auth.cpython-312.pyc +0 -0
  7. src/apps/__pycache__/database.cpython-312.pyc +0 -0
  8. src/apps/__pycache__/email_utils.cpython-312.pyc +0 -0
  9. src/apps/__pycache__/models.cpython-312.pyc +0 -0
  10. src/apps/app.py +520 -0
  11. src/apps/auth.py +42 -0
  12. src/apps/database.py +40 -0
  13. src/apps/email_utils.py +65 -0
  14. src/apps/models.py +32 -0
  15. src/apps/static/2023050195.pdf +3 -0
  16. src/apps/static/LegalTheory_CaseLawNotes.pdf +3 -0
  17. src/apps/static/MootCourt_Advocacy.pdf +3 -0
  18. src/apps/static/css/entrance.css +125 -0
  19. src/apps/static/css/modern-chat.css +609 -0
  20. src/apps/static/css/styles.css +87 -0
  21. src/apps/static/images/favicon.png +0 -0
  22. src/apps/static/images/judge.jpg +0 -0
  23. src/apps/static/images/lawbackground.jpg +0 -0
  24. src/apps/static/images/woman.jpeg +0 -0
  25. src/apps/static/js/entrance.js +132 -0
  26. src/apps/static/js/roleselection.js +34 -0
  27. src/apps/static/legal writing.pdf +3 -0
  28. src/apps/static/video.mp4 +3 -0
  29. src/apps/static/wipo_guide_ipc_2019.pdf +3 -0
  30. src/apps/templates/FIR.html +192 -0
  31. src/apps/templates/Judgechatbot.html +875 -0
  32. src/apps/templates/advocatechatbot.html +894 -0
  33. src/apps/templates/advocatedashboard.html +440 -0
  34. src/apps/templates/advocateresources.html +297 -0
  35. src/apps/templates/citizen.html +791 -0
  36. src/apps/templates/forgot_password.html +119 -0
  37. src/apps/templates/judgecalender.html +164 -0
  38. src/apps/templates/judgedashboard.html +697 -0
  39. src/apps/templates/legalrights.html +186 -0
  40. src/apps/templates/login.html +295 -0
  41. src/apps/templates/minor.html +897 -0
  42. src/apps/templates/register.html +139 -0
  43. src/apps/templates/reset_password.html +137 -0
  44. src/apps/templates/resources.html +213 -0
  45. src/apps/templates/roleselection.html +51 -0
  46. src/apps/templates/safetytips.html +186 -0
  47. src/apps/templates/script.js +42 -0
  48. src/apps/templates/studentchatbot.html +855 -0
  49. src/apps/templates/studentdashboard.html +540 -0
  50. src/apps/templates/viewall.html +253 -0
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ src/apps/static/2023050195.pdf filter=lfs diff=lfs merge=lfs -text
37
+ src/apps/static/legal[[:space:]]writing.pdf filter=lfs diff=lfs merge=lfs -text
38
+ src/apps/static/LegalTheory_CaseLawNotes.pdf filter=lfs diff=lfs merge=lfs -text
39
+ src/apps/static/MootCourt_Advocacy.pdf filter=lfs diff=lfs merge=lfs -text
40
+ src/apps/static/video.mp4 filter=lfs diff=lfs merge=lfs -text
41
+ src/apps/static/wipo_guide_ipc_2019.pdf filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set environment variables
4
+ ENV PYTHONDONTWRITEBYTECODE 1
5
+ ENV PYTHONUNBUFFERED 1
6
+ ENV KMP_DUPLICATE_LIB_OK=TRUE
7
+ ENV FAISS_NO_OPENMP=1
8
+
9
+ # Set work directory
10
+ WORKDIR /app
11
+
12
+ # Install system dependencies
13
+ RUN apt-get update && apt-get install -y --no-install-recommends \
14
+ build-essential \
15
+ libpq-dev \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # Install Python dependencies
19
+ COPY requirements.txt .
20
+ RUN pip install --no-cache-dir -r requirements.txt
21
+
22
+ # Copy project files
23
+ COPY . .
24
+
25
+ # Move to the apps directory
26
+ WORKDIR /app/src/apps
27
+
28
+ # Expose the port (Hugging Face Spaces uses 7860)
29
+ EXPOSE 7860
30
+
31
+ # Run the application
32
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
requirements.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ sqlalchemy==2.0.23
4
+ asyncpg==0.29.0
5
+ python-jose[cryptography]==3.3.0
6
+ passlib[bcrypt]==1.7.4
7
+ python-multipart==0.0.9
8
+ pydantic==2.5.0
9
+ pydantic-settings==2.1.0
10
+ python-dotenv==1.0.0
11
+ jinja2==3.1.2
12
+ aiofiles==23.2.1
13
+ requests==2.31.0
14
+ openai==1.3.0
15
+ langchain==0.1.0
16
+ langchain-community==0.0.10
17
+ chromadb==0.4.18
18
+ fastapi-mail>=1.5.0
19
+ httpx==0.25.1
20
+ faiss-cpu==1.7.4
21
+ transformers==4.35.2
22
+ torch==2.1.1
23
+ numpy>=1.24.0
24
+ einops==0.7.0
25
+ psycopg2-binary==2.9.9
src/apps/.env ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Database Configuration
2
+ DB_USER=postgres
3
+ DB_PASSWORD=Vishwa12
4
+ DB_HOST=localhost
5
+ DB_PORT=5432
6
+ DB_NAME=lawbot_db
7
+
8
+ # Security
9
+ SECRET_KEY=f1fda542576b459ee24bf2d459711a439576da2d804dcbb35de9e18261
10
+ ALGORITHM=HS256
11
+ ACCESS_TOKEN_EXPIRE_MINUTES=30
12
+
13
+ # Email Configuration (Gmail)
14
+ MAIL_USERNAME=vishwaroman04@gmail.com
15
+ MAIL_PASSWORD=vowg uhgs uwwh qspj
16
+ MAIL_FROM=vishwaroman04@gmail.com
17
+ MAIL_PORT=587
18
+ MAIL_SERVER=smtp.gmail.com
19
+
20
+ # OpenRouter API Key (for chatbot - get from https://openrouter.ai/keys)
21
+ # Use format: sk-or-v1-xxxxxxxx... (prefix only ONCE)
22
+ OPENROUTER_API_KEY=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
23
+
24
+
25
+
26
+
src/apps/__pycache__/app.cpython-312.pyc ADDED
Binary file (31.8 kB). View file
 
src/apps/__pycache__/auth.cpython-312.pyc ADDED
Binary file (2.23 kB). View file
 
src/apps/__pycache__/database.cpython-312.pyc ADDED
Binary file (1.78 kB). View file
 
src/apps/__pycache__/email_utils.cpython-312.pyc ADDED
Binary file (3.68 kB). View file
 
src/apps/__pycache__/models.cpython-312.pyc ADDED
Binary file (1.71 kB). View file
 
src/apps/app.py ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import bcrypt
2
+ # Robust monkeypatch for bcrypt-passlib compatibility
3
+ if not hasattr(bcrypt, "__about__"):
4
+ bcrypt.__about__ = type('About', (object,), {'__version__': bcrypt.__version__})
5
+
6
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, Response, Depends, HTTPException, status
7
+ from typing import Optional
8
+ from fastapi.responses import HTMLResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi.templating import Jinja2Templates
11
+ import uvicorn
12
+ import asyncio
13
+ import os
14
+ from sqlalchemy.ext.asyncio import AsyncSession
15
+ from sqlalchemy.future import select
16
+ from pydantic import BaseModel, EmailStr
17
+
18
+ from utils.main import RAG
19
+ from database import engine, Base, get_db
20
+ from models import User
21
+ from auth import get_password_hash, verify_password, create_access_token, ACCESS_TOKEN_EXPIRE_MINUTES, ALGORITHM, SECRET_KEY
22
+ from email_utils import send_reset_email
23
+ from datetime import timedelta
24
+ from fastapi.security import OAuth2PasswordBearer
25
+ from jose import JWTError, jwt
26
+ import uuid
27
+
28
+ # Initialize Database
29
+ async def init_db():
30
+ async with engine.begin() as conn:
31
+ await conn.run_sync(Base.metadata.create_all)
32
+
33
+ app = FastAPI(on_startup=[init_db])
34
+
35
+ chat_history = []
36
+
37
+ # Get the base directory
38
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39
+ STATIC_DIR = os.path.join(BASE_DIR, "static")
40
+ TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
41
+
42
+ # Mount static directories
43
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
44
+ app.mount("/images", StaticFiles(directory=os.path.join(STATIC_DIR, "images")), name="images")
45
+
46
+ # Initialize Jinja2 templates
47
+ templates = Jinja2Templates(directory=TEMPLATES_DIR)
48
+
49
+
50
+ # Pydantic Models for Auth
51
+ class UserCreate(BaseModel):
52
+ username: str
53
+ email: EmailStr
54
+ password: str
55
+ role: str
56
+
57
+ class UserLogin(BaseModel):
58
+ username: str
59
+ password: str
60
+ role: Optional[str] = None
61
+
62
+ class ForgotPassword(BaseModel):
63
+ email: EmailStr
64
+
65
+ class ResetPassword(BaseModel):
66
+ token: str
67
+ new_password: str
68
+
69
+ class Interaction(BaseModel):
70
+ caseId: str
71
+ query: str
72
+ response: str
73
+ role: str # Role context for this interaction
74
+
75
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login")
76
+
77
+ async def get_current_user(request: Request, db: AsyncSession = Depends(get_db)):
78
+ credentials_exception = HTTPException(
79
+ status_code=status.HTTP_401_UNAUTHORIZED,
80
+ detail="Could not validate credentials",
81
+ headers={"WWW-Authenticate": "Bearer"},
82
+ )
83
+
84
+ # Try to get token from Header first (for API calls), then Cookie (for Page navigation)
85
+ token = None
86
+ auth_header = request.headers.get("Authorization")
87
+ if auth_header and auth_header.startswith("Bearer "):
88
+ token = auth_header.split(" ")[1]
89
+ else:
90
+ token = request.cookies.get("access_token")
91
+
92
+ if not token:
93
+ raise credentials_exception
94
+
95
+ try:
96
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
97
+ username: str = payload.get("sub")
98
+ if username is None:
99
+ raise credentials_exception
100
+ except JWTError:
101
+ raise credentials_exception
102
+
103
+ result = await db.execute(select(User).where(User.username == username))
104
+ user = result.scalars().first()
105
+ if user is None:
106
+ raise credentials_exception
107
+ return user
108
+
109
+ def role_required(required_role: str):
110
+ async def role_checker(user: User = Depends(get_current_user)):
111
+ if user.role != required_role and user.role != "Admin":
112
+ raise HTTPException(
113
+ status_code=status.HTTP_403_FORBIDDEN,
114
+ detail=f"Access denied: Requires {required_role} role (or Admin)"
115
+ )
116
+ return user
117
+ return role_checker
118
+
119
+ # Auth Routes
120
+ @app.post("/api/register")
121
+ async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
122
+ result = await db.execute(select(User).where((User.username == user.username) | (User.email == user.email)))
123
+ if result.scalars().first():
124
+ raise HTTPException(status_code=400, detail="Username or Email already registered")
125
+
126
+ hashed_password = get_password_hash(user.password)
127
+ new_user = User(username=user.username, email=user.email, hashed_password=hashed_password, role=user.role)
128
+ db.add(new_user)
129
+ await db.commit()
130
+ return {"message": "User created successfully"}
131
+
132
+ @app.post("/api/login")
133
+ async def login(response: Response, user: UserLogin, db: AsyncSession = Depends(get_db)):
134
+ print(f"Login attempt for username: {user.username}")
135
+ result = await db.execute(select(User).where(User.username == user.username))
136
+ db_user = result.scalars().first()
137
+
138
+ if not db_user:
139
+ print(f"User not found: {user.username}")
140
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
141
+
142
+ print(f"User found: {db_user.username}, checking password...")
143
+ password_valid = verify_password(user.password, db_user.hashed_password)
144
+ print(f"Password valid: {password_valid}")
145
+
146
+ if not password_valid:
147
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
148
+
149
+ # Verify role if provided
150
+ if user.role and db_user.role != user.role and db_user.role != "Admin":
151
+ print(f"Role mismatch: user has {db_user.role}, but tried to login as {user.role}")
152
+ raise HTTPException(status_code=403, detail=f"Access denied: This account is registered as {db_user.role}, not {user.role}")
153
+
154
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
155
+ access_token = create_access_token(data={"sub": db_user.username, "role": db_user.role}, expires_delta=access_token_expires)
156
+
157
+ # Set secure cookie
158
+ response.set_cookie(
159
+ key="access_token",
160
+ value=access_token,
161
+ httponly=True,
162
+ max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
163
+ samesite="lax",
164
+ secure=False # Set to True in production with HTTPS
165
+ )
166
+
167
+ print(f"Login successful for {db_user.username}, role: {db_user.role}")
168
+ return {
169
+ "access_token": access_token,
170
+ "token_type": "bearer",
171
+ "role": db_user.role,
172
+ "question_count": db_user.question_count,
173
+ "is_admin": db_user.role == "Admin"
174
+ }
175
+
176
+ @app.post("/api/logout")
177
+ async def logout(response: Response):
178
+ response.delete_cookie("access_token")
179
+ return {"message": "Logged out successfully"}
180
+
181
+ @app.post("/api/forgot-password")
182
+ async def forgot_password(request: ForgotPassword, db: AsyncSession = Depends(get_db)):
183
+ result = await db.execute(select(User).where(User.email == request.email))
184
+ user = result.scalars().first()
185
+
186
+ if user:
187
+ token = str(uuid.uuid4())
188
+ user.reset_token = token
189
+ await db.commit()
190
+ try:
191
+ await send_reset_email(user.email, token)
192
+ except Exception as e:
193
+ print(f"Error sending email: {e}")
194
+ raise HTTPException(status_code=500, detail=f"Failed to send email: {str(e)}")
195
+
196
+ return {"message": "If an account exists, a reset email has been sent"}
197
+
198
+ @app.post("/api/reset-password")
199
+ async def reset_password(request: ResetPassword, db: AsyncSession = Depends(get_db)):
200
+ result = await db.execute(select(User).where(User.reset_token == request.token))
201
+ user = result.scalars().first()
202
+
203
+ if not user:
204
+ raise HTTPException(status_code=400, detail="Invalid or expired reset token")
205
+
206
+ user.hashed_password = get_password_hash(request.new_password)
207
+ user.reset_token = None
208
+ await db.commit()
209
+ return {"message": "Password reset successful"}
210
+
211
+ @app.post("/api/save-interaction")
212
+ async def save_interaction(interaction: Interaction, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
213
+ from models import ChatInteraction
214
+
215
+ # Create new chat interaction record linked to user with role
216
+ new_interaction = ChatInteraction(
217
+ case_id=interaction.caseId,
218
+ query=interaction.query,
219
+ response=interaction.response,
220
+ role=interaction.role,
221
+ user_id=current_user.id
222
+ )
223
+
224
+ db.add(new_interaction)
225
+ await db.commit()
226
+ await db.refresh(new_interaction)
227
+
228
+ print(f"Saved interaction for user {current_user.username}: ID={new_interaction.id}, CaseID={interaction.caseId}")
229
+ return {"status": "success", "message": "Interaction saved", "id": new_interaction.id}
230
+
231
+ @app.get("/api/interactions")
232
+ async def get_interactions(role: Optional[str] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
233
+ from models import ChatInteraction
234
+ from sqlalchemy import func
235
+
236
+ # Build base where clause
237
+ where_clause = ChatInteraction.user_id == current_user.id
238
+ if role:
239
+ where_clause = (ChatInteraction.user_id == current_user.id) & (ChatInteraction.role == role)
240
+
241
+ # Subquery to find the latest interaction per case_id (filtered by role if specified)
242
+ subquery = (
243
+ select(
244
+ ChatInteraction.case_id,
245
+ func.max(ChatInteraction.created_at).label("max_created")
246
+ )
247
+ .where(where_clause)
248
+ .group_by(ChatInteraction.case_id)
249
+ .subquery()
250
+ )
251
+
252
+ # Join to get query detail for the latest message in each case
253
+ result = await db.execute(
254
+ select(ChatInteraction)
255
+ .join(subquery, (ChatInteraction.case_id == subquery.c.case_id) & (ChatInteraction.created_at == subquery.c.max_created))
256
+ .order_by(ChatInteraction.created_at.desc())
257
+ .limit(20)
258
+ )
259
+ interactions = result.scalars().all()
260
+ return [{"id": i.id, "case_id": i.case_id, "query": i.query, "created_at": i.created_at} for i in interactions]
261
+
262
+ @app.get("/api/interactions/{case_id}")
263
+ async def get_conversation_thread(case_id: str, role: Optional[str] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
264
+ from models import ChatInteraction
265
+
266
+ # Build where clause with optional role filtering
267
+ where_clause = (ChatInteraction.user_id == current_user.id) & (ChatInteraction.case_id == case_id)
268
+ if role:
269
+ where_clause = where_clause & (ChatInteraction.role == role)
270
+
271
+ result = await db.execute(
272
+ select(ChatInteraction)
273
+ .where(where_clause)
274
+ .order_by(ChatInteraction.created_at.asc())
275
+ )
276
+ interactions = result.scalars().all()
277
+ if not interactions:
278
+ raise HTTPException(status_code=404, detail="Conversation not found")
279
+
280
+ return [{"query": i.query, "response": i.response, "created_at": i.created_at} for i in interactions]
281
+
282
+ @app.delete("/api/interactions/{case_id}")
283
+ async def delete_conversation(case_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
284
+ from models import ChatInteraction
285
+ from sqlalchemy import delete as sqlalchemy_delete
286
+
287
+ await db.execute(
288
+ sqlalchemy_delete(ChatInteraction)
289
+ .where((ChatInteraction.user_id == current_user.id) & (ChatInteraction.case_id == case_id))
290
+ )
291
+ await db.commit()
292
+ return {"status": "success", "message": "Conversation deleted"}
293
+
294
+ @app.get("/api/user-status")
295
+ async def get_user_status(current_user: User = Depends(get_current_user)):
296
+ return {
297
+ "username": current_user.username,
298
+ "role": current_user.role,
299
+ "question_count": current_user.question_count,
300
+ "limit": 2 if current_user.role != "Admin" else None,
301
+ "is_admin": current_user.role == "Admin"
302
+ }
303
+
304
+ # Frontend Routes for Auth
305
+ @app.get("/login", response_class=HTMLResponse)
306
+ async def login_page(request: Request):
307
+ return templates.TemplateResponse("login.html", {"request": request})
308
+
309
+ @app.get("/register", response_class=HTMLResponse)
310
+ async def register_page(request: Request):
311
+ return templates.TemplateResponse("register.html", {"request": request})
312
+
313
+ @app.get("/forgot-password", response_class=HTMLResponse)
314
+ async def forgot_password_page(request: Request):
315
+ return templates.TemplateResponse("forgot_password.html", {"request": request})
316
+
317
+ @app.get("/reset-password", response_class=HTMLResponse)
318
+ async def reset_password_page(request: Request):
319
+ return templates.TemplateResponse("reset_password.html", {"request": request})
320
+
321
+ # Home and Role Selection
322
+ @app.get("/", response_class=HTMLResponse)
323
+ async def role_selection(request: Request):
324
+ return templates.TemplateResponse("roleselection.html", {"request": request})
325
+
326
+ @app.get("/role", response_class=HTMLResponse)
327
+ async def roleselection_page(request: Request):
328
+ return templates.TemplateResponse("roleselection.html", {"request": request})
329
+
330
+ # Chatbot Pages
331
+ @app.get("/judgechatbot.html", response_class=HTMLResponse)
332
+ async def judge_chatbot(request: Request, user: User = Depends(role_required("Judge"))):
333
+ return templates.TemplateResponse("Judgechatbot.html", {"request": request})
334
+
335
+ @app.get("/judgedashboard.html", response_class=HTMLResponse)
336
+ async def judge_dashboard(request: Request, user: User = Depends(role_required("Judge"))):
337
+ return templates.TemplateResponse("judgedashboard.html", {"request": request})
338
+
339
+ @app.get("/viewall.html", response_class=HTMLResponse)
340
+ async def view_all(request: Request, user: User = Depends(role_required("Judge"))):
341
+ return templates.TemplateResponse("viewall.html", {"request": request})
342
+
343
+ @app.get("/judgecalender.html", response_class=HTMLResponse)
344
+ async def judge_calender(request: Request, user: User = Depends(role_required("Judge"))):
345
+ return templates.TemplateResponse("judgecalender.html", {"request": request})
346
+
347
+ @app.get("/advocatedashboard.html", response_class=HTMLResponse)
348
+ async def advocate_dashboard(request: Request, user: User = Depends(role_required("Advocate/Lawyer"))):
349
+ return templates.TemplateResponse("advocatedashboard.html", {"request": request})
350
+
351
+ @app.get("/advocateresources.html", response_class=HTMLResponse)
352
+ async def advocate_resources(request: Request, user: User = Depends(role_required("Advocate/Lawyer"))):
353
+ return templates.TemplateResponse("advocateresources.html", {"request": request})
354
+
355
+ # ========== Other Role Pages ==========
356
+ @app.get("/woman.html", response_class=HTMLResponse)
357
+ async def woman_page(request: Request, user: User = Depends(role_required("Woman"))):
358
+ return templates.TemplateResponse("woman.html", {"request": request})
359
+
360
+ @app.get("/citizen.html", response_class=HTMLResponse)
361
+ async def citizen_page(request: Request, user: User = Depends(role_required("Citizen"))):
362
+ return templates.TemplateResponse("citizen.html", {"request": request})
363
+
364
+ @app.get("/minor.html", response_class=HTMLResponse)
365
+ async def minor_page(request: Request, user: User = Depends(role_required("Minor"))):
366
+ return templates.TemplateResponse("minor.html", {"request": request})
367
+
368
+ # ========== Chatbot Pages ==========
369
+ @app.get("/studentchatbot.html", response_class=HTMLResponse)
370
+ async def student_page(request: Request, user: User = Depends(role_required("Student"))):
371
+ return templates.TemplateResponse("studentchatbot.html", {"request": request})
372
+
373
+ @app.get("/advocatechatbot.html", response_class=HTMLResponse)
374
+ async def advocatechatbot_page(request: Request):
375
+ return templates.TemplateResponse("advocatechatbot.html", {"request": request})
376
+
377
+ @app.get("/womanchatbot.html", response_class=HTMLResponse)
378
+ async def womanchatbot_page(request: Request):
379
+ return templates.TemplateResponse("womanchatbot.html", {"request": request})
380
+
381
+
382
+
383
+ @app.get("/safetytips.html", response_class=HTMLResponse)
384
+ async def safetytips_page(request: Request):
385
+ return templates.TemplateResponse("safetytips.html", {"request": request})
386
+
387
+ @app.get("/resources.html", response_class=HTMLResponse)
388
+ async def resources_page(request: Request):
389
+ return templates.TemplateResponse("resources.html", {"request": request})
390
+
391
+ @app.get("/legalrights.html", response_class=HTMLResponse)
392
+ async def legalrights_page(request: Request):
393
+ return templates.TemplateResponse("legalrights.html", {"request": request})
394
+
395
+ @app.get("/FIR.html", response_class=HTMLResponse)
396
+ async def fir_page(request: Request):
397
+ return templates.TemplateResponse("FIR.html", {"request": request})
398
+
399
+ @app.get("/studentdashboard.html", response_class=HTMLResponse)
400
+ async def student_page(request: Request):
401
+ return templates.TemplateResponse("studentdashboard.html", {"request": request})
402
+
403
+ # WebSocket for Chatbot
404
+ async def stream_text_conversational(websocket: WebSocket, query: str, role: str = "General"):
405
+ chat_limit = 10
406
+ temp_chat = {"user": "" ,"system":""}
407
+ temp_chat["user"] = query
408
+ # print(f"DEBUG: stream_text_conversational executing with role={role}")
409
+ model_response = ""
410
+ try:
411
+ if role == "Citizen":
412
+ completion = RAG(query, chat_history, role=role)
413
+ else:
414
+ completion = RAG(query, chat_history, role=role)
415
+ for chunk in completion:
416
+ if chunk.choices[0].delta.content is not None:
417
+ await websocket.send_text(chunk.choices[0].delta.content)
418
+ await asyncio.sleep(0.01)
419
+ model_response += chunk.choices[0].delta.content
420
+
421
+ # Signal completion to frontend
422
+ await websocket.send_text("[DONE]")
423
+
424
+ # print(model_response)
425
+ temp_chat['system']=model_response
426
+ chat_history.append(temp_chat)
427
+ if len(chat_history)>chat_limit:
428
+ chat_history.pop(0)
429
+ except Exception as e:
430
+ error_message = f"Error: {str(e)}"
431
+ print(f"Chatbot error: {error_message}")
432
+ await websocket.send_text(f"\n\n⚠️ {error_message}\n\nPlease check your API key configuration.")
433
+ raise
434
+
435
+ @app.websocket("/conversational_chat")
436
+ async def conversational_chat(websocket: WebSocket, role: Optional[str] = None):
437
+ await websocket.accept()
438
+
439
+ # --- STRICT ROLE ENFORCEMENT ---
440
+ # 1. Get role from Token/Cookie (Source of Truth)
441
+ token = websocket.cookies.get("access_token")
442
+ token_role = "General"
443
+ user_id = None
444
+ question_count = 0
445
+
446
+ if token:
447
+ try:
448
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
449
+ username = payload.get("sub")
450
+ if username:
451
+ async for db in get_db():
452
+ result = await db.execute(select(User).where(User.username == username))
453
+ user_obj = result.scalars().first()
454
+ if user_obj:
455
+ token_role = user_obj.role
456
+ user_id = user_obj.id
457
+ question_count = user_obj.question_count
458
+ break
459
+ except JWTError:
460
+ pass
461
+
462
+ if user_id is None:
463
+ await websocket.send_text("\n\n⚠️ Authentication Required. Please sign in to use the chatbot.")
464
+ await websocket.close()
465
+ return
466
+
467
+ # 2. Apply Logic based on Source of Truth
468
+ if token_role == "Admin":
469
+ if not role:
470
+ role = "Admin"
471
+ else:
472
+ role = token_role
473
+
474
+ while True:
475
+ try:
476
+ query = await websocket.receive_text()
477
+
478
+ # --- USAGE LIMIT CHECK ---
479
+ if token_role != "Admin" and question_count >= 2:
480
+ limit_message = (
481
+ "### Free usage limit reached\n\n"
482
+ "You’ve reached the free usage limit (2 questions).\n"
483
+ "Further access is restricted.\n\n"
484
+ "Please contact the administrator for extended access:\n"
485
+ "LinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)"
486
+ )
487
+ await websocket.send_text(limit_message)
488
+ continue
489
+
490
+ print(f"Query ({role}): {query}")
491
+ await stream_text_conversational(websocket, query, role=role)
492
+
493
+ # --- INCREMENT USAGE ---
494
+ if token_role != "Admin":
495
+ async for db in get_db():
496
+ result = await db.execute(select(User).where(User.id == user_id))
497
+ user_to_update = result.scalars().first()
498
+ if user_to_update:
499
+ user_to_update.question_count += 1
500
+ question_count = user_to_update.question_count # Keep local sync
501
+ await db.commit()
502
+ break
503
+
504
+ except WebSocketDisconnect:
505
+ chat_history.clear()
506
+ break
507
+ except Exception as e:
508
+ print(f"WebSocket error: {e}")
509
+ try:
510
+ await websocket.send_text(f"\n\n❌ Connection error: {str(e)}\n\nPlease try again or contact support.")
511
+ except:
512
+ pass
513
+ break
514
+
515
+
516
+ # Run the application
517
+ if __name__ == "__main__":
518
+ print("Starting Law Bot Server...")
519
+ port = int(os.getenv("PORT", 8000))
520
+ uvicorn.run(app, host="0.0.0.0", port=port)
src/apps/auth.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional
3
+ import bcrypt
4
+
5
+ # Monkeypatch bcrypt for passlib compatibility if needed
6
+ if not hasattr(bcrypt, "__about__"):
7
+ bcrypt.__about__ = type('About', (object,), {'__version__': bcrypt.__version__})
8
+
9
+ from jose import JWTError, jwt
10
+ from passlib.context import CryptContext
11
+ import os
12
+ from dotenv import load_dotenv
13
+
14
+ load_dotenv()
15
+
16
+ SECRET_KEY = os.getenv("SECRET_KEY", "your_secret_key")
17
+ ALGORITHM = os.getenv("ALGORITHM", "HS256")
18
+ ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 30))
19
+
20
+ _pwd_context = None
21
+
22
+ def get_pwd_context():
23
+ global _pwd_context
24
+ if _pwd_context is None:
25
+ _pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
26
+ return _pwd_context
27
+
28
+ def verify_password(plain_password, hashed_password):
29
+ return get_pwd_context().verify(plain_password, hashed_password)
30
+
31
+ def get_password_hash(password):
32
+ return get_pwd_context().hash(password)
33
+
34
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
35
+ to_encode = data.copy()
36
+ if expires_delta:
37
+ expire = datetime.utcnow() + expires_delta
38
+ else:
39
+ expire = datetime.utcnow() + timedelta(minutes=15)
40
+ to_encode.update({"exp": expire})
41
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
42
+ return encoded_jwt
src/apps/database.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
2
+ from sqlalchemy.orm import sessionmaker, declarative_base
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ # Support both DATABASE_URL (Supabase/production) and individual env vars (local dev)
9
+ DATABASE_URL = os.getenv("DATABASE_URL")
10
+
11
+ if DATABASE_URL:
12
+ # Use DATABASE_URL if provided (Supabase/Vercel)
13
+ # Convert postgres:// to postgresql+asyncpg:// if needed
14
+ if DATABASE_URL.startswith("postgres://"):
15
+ DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql+asyncpg://", 1)
16
+ elif DATABASE_URL.startswith("postgresql://"):
17
+ DATABASE_URL = DATABASE_URL.replace("postgresql://", "postgresql+asyncpg://", 1)
18
+ else:
19
+ # Fall back to individual environment variables (local development)
20
+ DB_USER = os.getenv("DB_USER", "postgres")
21
+ DB_PASSWORD = os.getenv("DB_PASSWORD", "password")
22
+ DB_HOST = os.getenv("DB_HOST", "localhost")
23
+ DB_PORT = os.getenv("DB_PORT", "5432")
24
+ DB_NAME = os.getenv("DB_NAME", "lawbot_db")
25
+ DATABASE_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
26
+
27
+
28
+ engine = create_async_engine(DATABASE_URL, echo=True)
29
+
30
+ AsyncSessionLocal = sessionmaker(
31
+ bind=engine,
32
+ class_=AsyncSession,
33
+ expire_on_commit=False,
34
+ )
35
+
36
+ Base = declarative_base()
37
+
38
+ async def get_db():
39
+ async with AsyncSessionLocal() as session:
40
+ yield session
src/apps/email_utils.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType
2
+ from pydantic import EmailStr
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ BASE_URL = os.getenv("BASE_URL", "http://localhost:8000")
9
+
10
+ # Support both SMTP_* and MAIL_* environment variable naming conventions
11
+ MAIL_USERNAME = os.getenv("MAIL_USERNAME") or os.getenv("SMTP_USERNAME", "")
12
+ MAIL_PASSWORD = os.getenv("MAIL_PASSWORD") or os.getenv("SMTP_PASSWORD", "")
13
+ MAIL_FROM = os.getenv("MAIL_FROM") or os.getenv("FROM_EMAIL", "")
14
+ MAIL_PORT = int(os.getenv("MAIL_PORT") or os.getenv("SMTP_PORT", "587"))
15
+ MAIL_SERVER = os.getenv("MAIL_SERVER") or os.getenv("SMTP_SERVER", "smtp.gmail.com")
16
+
17
+ conf = ConnectionConfig(
18
+ MAIL_USERNAME=MAIL_USERNAME,
19
+ MAIL_PASSWORD=MAIL_PASSWORD,
20
+ MAIL_FROM=MAIL_FROM,
21
+ MAIL_PORT=MAIL_PORT,
22
+ MAIL_SERVER=MAIL_SERVER,
23
+ MAIL_STARTTLS=True,
24
+ MAIL_SSL_TLS=False,
25
+ USE_CREDENTIALS=True,
26
+ VALIDATE_CERTS=True
27
+ )
28
+
29
+ async def send_reset_email(email: EmailStr, token: str):
30
+ # Validate email configuration before attempting to send
31
+ if not MAIL_USERNAME or not MAIL_PASSWORD or not MAIL_FROM:
32
+ missing_vars = []
33
+ if not MAIL_USERNAME:
34
+ missing_vars.append("MAIL_USERNAME or SMTP_USERNAME")
35
+ if not MAIL_PASSWORD:
36
+ missing_vars.append("MAIL_PASSWORD or SMTP_PASSWORD")
37
+ if not MAIL_FROM:
38
+ missing_vars.append("MAIL_FROM or FROM_EMAIL")
39
+ raise ValueError(f"Email configuration missing: {', '.join(missing_vars)}. Please set these environment variables.")
40
+
41
+ reset_link = f"{BASE_URL}/reset-password?token={token}"
42
+ html_body = f"""
43
+ <html>
44
+ <body style="font-family: Arial, sans-serif; padding: 20px; background-color: #f4f4f4;">
45
+ <div style="max-width: 600px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px;">
46
+ <h2 style="color: #9b87f5;">Password Reset Request</h2>
47
+ <p>Hello,</p>
48
+ <p>We received a request to reset your password for your Law Bot account.</p>
49
+ <p>Click the button below to reset your password:</p>
50
+ <a href="{reset_link}" style="display: inline-block; padding: 12px 24px; background-color: #9b87f5; color: white; text-decoration: none; border-radius: 6px; margin: 20px 0;">Reset Password</a>
51
+ <p>Or copy and paste this link into your browser:</p>
52
+ <p style="color: #666; word-break: break-all;">{reset_link}</p>
53
+ <p style="color: #999; font-size: 12px; margin-top: 30px;">If you didn't request this, you can safely ignore this email.</p>
54
+ </div>
55
+ </body>
56
+ </html>
57
+ """
58
+ message = MessageSchema(
59
+ subject="Password Reset Request - Law Bot",
60
+ recipients=[email],
61
+ body=html_body,
62
+ subtype=MessageType.html
63
+ )
64
+ fm = FastMail(conf)
65
+ await fm.send_message(message)
src/apps/models.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
2
+ from sqlalchemy.orm import relationship
3
+ from datetime import datetime
4
+ from database import Base
5
+
6
+ class User(Base):
7
+ __tablename__ = "users"
8
+
9
+ id = Column(Integer, primary_key=True, index=True)
10
+ username = Column(String, unique=True, index=True)
11
+ email = Column(String, unique=True, index=True)
12
+ hashed_password = Column(String)
13
+ role = Column(String)
14
+ reset_token = Column(String, nullable=True)
15
+ question_count = Column(Integer, default=0)
16
+
17
+ # Relationship to chat interactions
18
+ interactions = relationship("ChatInteraction", back_populates="user")
19
+
20
+ class ChatInteraction(Base):
21
+ __tablename__ = "chat_interactions"
22
+
23
+ id = Column(Integer, primary_key=True, index=True)
24
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
25
+ case_id = Column(String, nullable=True)
26
+ role = Column(String, index=True, nullable=False) # Role context for this interaction
27
+ query = Column(Text)
28
+ response = Column(Text)
29
+ created_at = Column(DateTime, default=datetime.utcnow)
30
+
31
+ # Relationship to user
32
+ user = relationship("User", back_populates="interactions")
src/apps/static/2023050195.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4699ff98cece2142f96ce0dba62539e8219e658ba6b595efcb666caf2fcea26d
3
+ size 2078473
src/apps/static/LegalTheory_CaseLawNotes.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c80a22cffea38344f6b40125a5da25fc1ec66a73979424db9cce082b0807f2b0
3
+ size 777565
src/apps/static/MootCourt_Advocacy.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2dfae09ecdc3fc4d2ef2b6a6402db53289a50e028db4749e13920a7451722606
3
+ size 115883
src/apps/static/css/entrance.css ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Entrance Animations */
2
+ .animate-entrance {
3
+ opacity: 0;
4
+ animation-fill-mode: forwards;
5
+ }
6
+
7
+ @keyframes entrance-slide-in-left {
8
+ from {
9
+ transform: translateX(-40px);
10
+ opacity: 0;
11
+ }
12
+
13
+ to {
14
+ transform: translateX(0);
15
+ opacity: 1;
16
+ }
17
+ }
18
+
19
+ @keyframes entrance-slide-in-down {
20
+ from {
21
+ transform: translateY(-40px);
22
+ opacity: 0;
23
+ }
24
+
25
+ to {
26
+ transform: translateY(0);
27
+ opacity: 1;
28
+ }
29
+ }
30
+
31
+ @keyframes entrance-slide-in-up {
32
+ from {
33
+ transform: translateY(40px);
34
+ opacity: 0;
35
+ }
36
+
37
+ to {
38
+ transform: translateY(0);
39
+ opacity: 1;
40
+ }
41
+ }
42
+
43
+ .entrance-sidebar {
44
+ animation: entrance-slide-in-left 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
45
+ }
46
+
47
+ .entrance-header {
48
+ animation: entrance-slide-in-down 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s forwards;
49
+ }
50
+
51
+ .entrance-card {
52
+ animation: entrance-slide-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
53
+ }
54
+
55
+ .entrance-chatbot {
56
+ animation: entrance-slide-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.8s forwards;
57
+ }
58
+
59
+ /* --- PROFESSIONAL LOGOUT: Terminal Shutdown --- */
60
+
61
+ @keyframes component-disassemble {
62
+ from {
63
+ transform: scale(1);
64
+ filter: blur(0);
65
+ opacity: 1;
66
+ }
67
+
68
+ to {
69
+ transform: scale(0.95);
70
+ filter: blur(10px);
71
+ opacity: 0;
72
+ }
73
+ }
74
+
75
+ .exit-disassemble {
76
+ animation: component-disassemble 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
77
+ }
78
+
79
+
80
+
81
+ /* Logout Portal Overlay */
82
+ .logout-portal-overlay {
83
+ position: fixed;
84
+ top: 0;
85
+ left: 0;
86
+ width: 100%;
87
+ height: 100%;
88
+ background: rgba(5, 5, 10, 0.98);
89
+ backdrop-filter: blur(25px);
90
+ z-index: 99999;
91
+ display: flex;
92
+ justify-content: center;
93
+ align-items: center;
94
+ flex-direction: column;
95
+ color: white;
96
+ opacity: 0;
97
+ transition: opacity 0.5s ease;
98
+ pointer-events: none;
99
+ }
100
+
101
+ .logout-portal-overlay.show {
102
+ opacity: 1;
103
+ pointer-events: all;
104
+ }
105
+
106
+ .logout-portal-message {
107
+ font-size: 1.2rem;
108
+ font-weight: 500;
109
+ letter-spacing: 4px;
110
+ text-transform: uppercase;
111
+ margin-bottom: 20px;
112
+ animation: text-flicker 2s infinite;
113
+ }
114
+
115
+ @keyframes text-flicker {
116
+
117
+ 0%,
118
+ 100% {
119
+ opacity: 1;
120
+ }
121
+
122
+ 50% {
123
+ opacity: 0.7;
124
+ }
125
+ }
src/apps/static/css/modern-chat.css ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Modern Chat Enhancements */
2
+ :root {
3
+ --judge-color: #D4AF37;
4
+ /* Gold */
5
+ --advocate-color: #2c3e50;
6
+ /* Navy */
7
+ --woman-color: #ff7eb3;
8
+ /* Soft Pink */
9
+ --minor-color: #4ade80;
10
+ /* Fresh Green */
11
+ --student-color: #3498db;
12
+ /* Academic Blue */
13
+ --primary-purple: #9b87f5;
14
+ --light-purple: #D6BCFA;
15
+ --neutral-gray: #8E9196;
16
+ --light-gray: #C8C8C9;
17
+ --off-white: #eee;
18
+
19
+ /* Dark Theme (Default) */
20
+ --bg-main: #1A1F2C;
21
+ --sidebar-bg: rgba(0, 0, 0, 0.4);
22
+ --sidebar-border: rgba(255, 255, 255, 0.05);
23
+ --text-main: #eee;
24
+ --text-dim: rgba(255, 255, 255, 0.6);
25
+ --input-bg: #1A1F2C;
26
+ --item-bg: rgba(255, 255, 255, 0.02);
27
+ --footer-bg: #0d0d0d;
28
+ }
29
+
30
+ body.light {
31
+ --bg-main: #ffffff;
32
+ --sidebar-bg: #f8f9fa;
33
+ --sidebar-border: #e9ecef;
34
+ --text-main: #1A1F2C;
35
+ --text-dim: #495057;
36
+ --input-bg: #ffffff;
37
+ --item-bg: #ffffff;
38
+ --footer-bg: #f1f3f5;
39
+ }
40
+
41
+ body {
42
+ background-color: var(--bg-main) !important;
43
+ color: var(--text-main) !important;
44
+ transition: background-color 0.3s, color 0.3s;
45
+ }
46
+
47
+ /* Role Specific Body Themes */
48
+ body.role-judge {
49
+ --role-primary: var(--judge-color);
50
+ }
51
+
52
+ body.role-advocate {
53
+ --role-primary: var(--advocate-color);
54
+ }
55
+
56
+ body.role-woman {
57
+ --role-primary: var(--woman-color);
58
+ }
59
+
60
+ body.role-minor {
61
+ --role-primary: var(--minor-color);
62
+ }
63
+
64
+ body.role-student {
65
+ --role-primary: var(--student-color);
66
+ }
67
+
68
+ /* Evidence Box (Citations) */
69
+ .evidence-box {
70
+ margin-top: 1rem;
71
+ padding: 0.75rem;
72
+ background: rgba(255, 255, 255, 0.03);
73
+ border-left: 3px solid var(--role-primary, var(--primary-purple));
74
+ border-radius: 4px;
75
+ font-size: 0.85rem;
76
+ }
77
+
78
+ .evidence-title {
79
+ font-weight: 600;
80
+ color: var(--role-primary, var(--primary-purple));
81
+ margin-bottom: 0.4rem;
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 0.5rem;
85
+ }
86
+
87
+ .evidence-item {
88
+ color: rgba(255, 255, 255, 0.7);
89
+ margin-bottom: 0.2rem;
90
+ }
91
+
92
+ /* Loading Animation: Gavel or Pulse */
93
+ .typing-indicator {
94
+ display: none;
95
+ align-items: center;
96
+ gap: 10px;
97
+ padding: 10px 15px;
98
+ background: rgba(255, 255, 255, 0.05);
99
+ border-radius: 8px;
100
+ margin-bottom: 1rem;
101
+ width: fit-content;
102
+ }
103
+
104
+ .gavel-icon {
105
+ width: 20px;
106
+ height: 20px;
107
+ animation: hammer 0.8s infinite ease-in-out alternate;
108
+ }
109
+
110
+ @keyframes hammer {
111
+ 0% {
112
+ transform: rotate(0deg);
113
+ }
114
+
115
+ 100% {
116
+ transform: rotate(-45deg);
117
+ }
118
+ }
119
+
120
+ /* Legal Disclaimer */
121
+ .legal-footer {
122
+ text-align: center;
123
+ padding: 1rem;
124
+ font-size: 0.7rem;
125
+ color: var(--neutral-gray);
126
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
127
+ background: var(--bg-dark);
128
+ }
129
+
130
+
131
+ /* Sidebar Layout */
132
+ .sidebar {
133
+ position: fixed;
134
+ left: 0;
135
+ top: 0;
136
+ bottom: 0;
137
+ width: 260px;
138
+ background: var(--sidebar-bg);
139
+ border-right: 1px solid var(--sidebar-border);
140
+ padding: 2rem 1.5rem;
141
+ display: flex;
142
+ flex-direction: column;
143
+ z-index: 1000;
144
+ backdrop-filter: blur(15px);
145
+ }
146
+
147
+ .sidebar-title {
148
+ font-size: 0.75rem;
149
+ font-weight: 700;
150
+ color: var(--neutral-gray);
151
+ margin-bottom: 1.5rem;
152
+ text-transform: uppercase;
153
+ letter-spacing: 1.5px;
154
+ opacity: 0.6;
155
+ }
156
+
157
+ .history-list {
158
+ flex-grow: 1;
159
+ overflow-y: auto;
160
+ margin-bottom: 1rem;
161
+ padding-right: 0.5rem;
162
+ }
163
+
164
+ .history-list::-webkit-scrollbar {
165
+ width: 4px;
166
+ }
167
+
168
+ .history-list::-webkit-scrollbar-thumb {
169
+ background: rgba(255, 255, 255, 0.1);
170
+ border-radius: 10px;
171
+ }
172
+
173
+ .history-item {
174
+ padding: 0.85rem;
175
+ border-radius: 8px;
176
+ background: var(--item-bg);
177
+ margin-bottom: 0.75rem;
178
+ font-size: 0.85rem;
179
+ color: var(--text-dim);
180
+ cursor: pointer;
181
+ transition: all 0.2s ease;
182
+ border: 1px solid var(--sidebar-border);
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 0.75rem;
189
+ }
190
+
191
+ .history-item svg {
192
+ flex-shrink: 0;
193
+ opacity: 0.5;
194
+ }
195
+
196
+ .history-item:hover {
197
+ background: rgba(255, 255, 255, 0.06);
198
+ border-color: var(--role-primary, var(--primary-purple));
199
+ color: white;
200
+ }
201
+
202
+ .history-item:hover svg {
203
+ opacity: 1;
204
+ color: var(--role-primary, var(--primary-purple));
205
+ }
206
+
207
+ .sidebar-footer {
208
+ padding-top: 1.5rem;
209
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
210
+ margin-top: auto;
211
+ }
212
+
213
+ /* Fix for Back to Dashboard */
214
+ .sidebar .role-selection-link {
215
+ position: static !important;
216
+ display: flex !important;
217
+ align-items: center;
218
+ gap: 0.75rem;
219
+ font-size: 0.9rem;
220
+ font-weight: 500;
221
+ color: var(--role-primary, var(--primary-purple)) !important;
222
+ text-decoration: none;
223
+ transition: transform 0.2s ease;
224
+ }
225
+
226
+ .sidebar .role-selection-link:hover {
227
+ transform: translateX(5px);
228
+ }
229
+
230
+ /* Adjust main container to give room for sidebar */
231
+ .container {
232
+ margin-left: 260px !important;
233
+ max-width: calc(100% - 260px) !important;
234
+ }
235
+
236
+ /* AI Message Styling Enhancements */
237
+ .ai-message {
238
+ border-left: 4px solid var(--role-primary, var(--primary-purple)) !important;
239
+ }
240
+
241
+ /* --- SIDEBAR ENHANCEMENTS (Functional Only) --- */
242
+
243
+ .sidebar-header {
244
+ display: flex;
245
+ justify-content: space-between;
246
+ align-items: center;
247
+ margin-bottom: 1rem;
248
+ }
249
+
250
+ .new-chat-btn {
251
+ flex-grow: 1;
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 0.75rem;
255
+ padding: 0.6rem 1rem;
256
+ border: 1px solid var(--sidebar-border);
257
+ border-radius: 8px;
258
+ background: transparent;
259
+ color: var(--text-main);
260
+ font-size: 0.85rem;
261
+ font-weight: 500;
262
+ cursor: pointer;
263
+ transition: all 0.2s ease;
264
+ }
265
+
266
+ .new-chat-btn:hover {
267
+ background: rgba(255, 255, 255, 0.05);
268
+ border-color: var(--role-primary, var(--primary-purple));
269
+ }
270
+
271
+ .collapse-toggle {
272
+ background: transparent;
273
+ border: none;
274
+ color: rgba(255, 255, 255, 0.4);
275
+ cursor: pointer;
276
+ padding: 6px;
277
+ border-radius: 6px;
278
+ display: flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ transition: background 0.2s;
282
+ }
283
+
284
+ .collapse-toggle:hover {
285
+ background: rgba(255, 255, 255, 0.05);
286
+ color: white;
287
+ }
288
+
289
+ /* Sidebar Toggle (Floating button when collapsed) */
290
+ .sidebar-floating-toggle {
291
+ position: fixed;
292
+ left: 15px;
293
+ top: 15px;
294
+ z-index: 1100;
295
+ background: #1e2229;
296
+ /* Deep dark to match theme */
297
+ border: 1px solid rgba(255, 255, 255, 0.1);
298
+ border-radius: 8px;
299
+ padding: 8px;
300
+ color: white;
301
+ cursor: pointer;
302
+ display: none;
303
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
304
+ }
305
+
306
+ body.sidebar-collapsed .sidebar {
307
+ transform: translateX(-100%);
308
+ transition: transform 0.3s ease;
309
+ }
310
+
311
+ body.sidebar-collapsed .container {
312
+ margin-left: 0 !important;
313
+ max-width: 100% !important;
314
+ padding-left: 60px;
315
+ /* Space for the floating toggle */
316
+ }
317
+
318
+ body.sidebar-collapsed .sidebar-floating-toggle {
319
+ display: flex;
320
+ }
321
+
322
+ .sidebar {
323
+ transition: transform 0.3s ease;
324
+ }
325
+
326
+ /* Delete Icon per Chat Item */
327
+ .history-item {
328
+ position: relative;
329
+ padding-right: 2.5rem;
330
+ }
331
+
332
+ .delete-chat-item {
333
+ position: absolute;
334
+ right: 0.5rem;
335
+ top: 50%;
336
+ transform: translateY(-50%);
337
+ background: transparent;
338
+ border: none;
339
+ color: rgba(255, 255, 255, 0.3);
340
+ padding: 4px;
341
+ cursor: pointer;
342
+ opacity: 0;
343
+ transition: opacity 0.2s, color 0.2s;
344
+ }
345
+
346
+ .history-item:hover .delete-chat-item {
347
+ opacity: 1;
348
+ }
349
+
350
+ .delete-chat-item:hover {
351
+ color: #ff4757;
352
+ }
353
+
354
+ /* Answer Perspective Section */
355
+ .perspective-container {
356
+ margin-top: 1.5rem;
357
+ padding-top: 1rem;
358
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
359
+ }
360
+
361
+ .perspective-option {
362
+ padding: 0.6rem 0.8rem;
363
+ border-radius: 6px;
364
+ font-size: 0.85rem;
365
+ color: rgba(255, 255, 255, 0.5);
366
+ cursor: pointer;
367
+ transition: all 0.2s;
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 0.6rem;
371
+ }
372
+
373
+ .perspective-option:hover {
374
+ background: rgba(255, 255, 255, 0.03);
375
+ color: white;
376
+ }
377
+
378
+ .perspective-option.active {
379
+ background: rgba(255, 255, 255, 0.08);
380
+ color: var(--role-primary, var(--primary-purple));
381
+ font-weight: 600;
382
+ }
383
+
384
+ /* Fixed/Sticky Footer Fix */
385
+ .sidebar-footer {
386
+ position: sticky;
387
+ bottom: 0px;
388
+ background: var(--footer-bg);
389
+ padding: 1.5rem 0;
390
+ margin-top: auto;
391
+ z-index: 10;
392
+ }
393
+
394
+ /* --- MISSING LAYOUT STYLES --- */
395
+ .header {
396
+ display: flex;
397
+ justify-content: space-between;
398
+ align-items: center;
399
+ margin-bottom: 2rem;
400
+ }
401
+
402
+ .chat-container {
403
+ flex-grow: 1;
404
+ overflow-y: auto;
405
+ margin-bottom: 2rem;
406
+ padding: 1rem;
407
+ }
408
+
409
+ .input-container {
410
+ position: fixed;
411
+ bottom: 0;
412
+ left: 260px;
413
+ /* Offset for sidebar */
414
+ right: 0;
415
+ padding: 1rem;
416
+ background-color: var(--input-bg);
417
+ border-top: 1px solid var(--sidebar-border);
418
+ z-index: 900;
419
+ }
420
+
421
+ body.sidebar-collapsed .input-container {
422
+ left: 0;
423
+ }
424
+
425
+ .input-wrapper {
426
+ max-width: 800px;
427
+ margin: 0 auto;
428
+ position: relative;
429
+ }
430
+
431
+ #messageInput {
432
+ width: 100%;
433
+ padding: 1rem;
434
+ padding-right: 3rem;
435
+ border: 1px solid rgba(255, 255, 255, 0.1);
436
+ border-radius: 8px;
437
+ font-size: 1rem;
438
+ outline: none;
439
+ transition: border-color 0.3s;
440
+ background-color: transparent;
441
+ color: inherit;
442
+ }
443
+
444
+ #messageInput:focus {
445
+ border-color: var(--role-primary, var(--primary-purple));
446
+ }
447
+
448
+ #sendButton {
449
+ position: absolute;
450
+ right: 0.5rem;
451
+ top: 50%;
452
+ transform: translateY(-50%);
453
+ background: none;
454
+ border: none;
455
+ cursor: pointer;
456
+ padding: 0.5rem;
457
+ color: var(--role-primary, var(--primary-purple));
458
+ opacity: 0.8;
459
+ transition: opacity 0.3s;
460
+ }
461
+
462
+ #sendButton:hover {
463
+ opacity: 1;
464
+ }
465
+
466
+ .message {
467
+ margin-bottom: 1.5rem;
468
+ padding: 1rem;
469
+ border-radius: 8px;
470
+ animation: fadeIn 0.3s ease-in;
471
+ }
472
+
473
+ .user-message {
474
+ background-color: rgba(155, 135, 245, 0.1);
475
+ }
476
+
477
+ .ai-message {
478
+ background-color: rgba(255, 255, 255, 0.05);
479
+ border: 1px solid rgba(200, 200, 201, 0.2);
480
+ }
481
+
482
+ .message-header {
483
+ display: flex;
484
+ align-items: center;
485
+ margin-bottom: 0.5rem;
486
+ font-weight: 500;
487
+ }
488
+
489
+ .user-icon,
490
+ .ai-icon {
491
+ width: 24px;
492
+ height: 24px;
493
+ border-radius: 50%;
494
+ margin-right: 0.5rem;
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: center;
498
+ font-size: 12px;
499
+ }
500
+
501
+ .user-icon {
502
+ background-color: var(--primary-purple);
503
+ color: white;
504
+ }
505
+
506
+ .ai-icon {
507
+ background-color: var(--light-purple);
508
+ color: var(--dark-purple);
509
+ }
510
+
511
+ @keyframes fadeIn {
512
+ from {
513
+ opacity: 0;
514
+ transform: translateY(10px);
515
+ }
516
+
517
+ to {
518
+ opacity: 1;
519
+ transform: translateY(0);
520
+ }
521
+ }
522
+
523
+ /* --- USER PROFILE DROPDOWN --- */
524
+ .header-right {
525
+ display: flex;
526
+ align-items: center;
527
+ gap: 1rem;
528
+ }
529
+
530
+ .user-profile-menu {
531
+ position: relative;
532
+ display: flex;
533
+ align-items: center;
534
+ cursor: pointer;
535
+ }
536
+
537
+ .profile-icon-btn {
538
+ width: 36px;
539
+ height: 36px;
540
+ border-radius: 50%;
541
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
542
+ display: flex;
543
+ align-items: center;
544
+ justify-content: center;
545
+ color: white;
546
+ font-size: 1.2rem;
547
+ border: 2px solid rgba(255, 255, 255, 0.2);
548
+ transition: all 0.2s;
549
+ }
550
+
551
+ .profile-icon-btn:hover {
552
+ transform: scale(1.05);
553
+ border-color: var(--primary-purple);
554
+ }
555
+
556
+ .dropdown-menu {
557
+ position: absolute;
558
+ top: 120%;
559
+ right: 0;
560
+ width: 200px;
561
+ background: var(--bg-main);
562
+ border: 1px solid var(--sidebar-border);
563
+ border-radius: 8px;
564
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
565
+ overflow: hidden;
566
+ display: none;
567
+ z-index: 2000;
568
+ flex-direction: column;
569
+ }
570
+
571
+ .dropdown-menu.show {
572
+ display: flex;
573
+ animation: fadeIn 0.2s ease-out;
574
+ }
575
+
576
+ .dropdown-item {
577
+ padding: 0.8rem 1rem;
578
+ font-size: 0.9rem;
579
+ color: var(--text-main);
580
+ text-decoration: none;
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 0.8rem;
584
+ transition: background 0.2s;
585
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
586
+ }
587
+
588
+ .dropdown-item:last-child {
589
+ border-bottom: none;
590
+ }
591
+
592
+ .dropdown-item:hover {
593
+ background: rgba(255, 255, 255, 0.05);
594
+ }
595
+
596
+ .dropdown-item.role-info {
597
+ font-weight: 600;
598
+ color: var(--text-dim);
599
+ background: rgba(0, 0, 0, 0.2);
600
+ cursor: default;
601
+ }
602
+
603
+ .logout-action {
604
+ color: #ff4757;
605
+ }
606
+
607
+ .logout-action:hover {
608
+ background: rgba(255, 71, 87, 0.1);
609
+ }
src/apps/static/css/styles.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Import Google Font */
2
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap');
3
+
4
+ /* Reset and base styles */
5
+ body, html {
6
+ margin: 0;
7
+ padding: 0;
8
+ height: 100%;
9
+ font-family: 'Roboto', sans-serif;
10
+ background: url('/images/lawbackground.jpg') no-repeat center center fixed;
11
+ background-size: cover;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ }
16
+
17
+ /* Overlay to darken the background image */
18
+ .overlay {
19
+ position: fixed;
20
+ top: 0;
21
+ left: 0;
22
+ width: 100%;
23
+ height: 100%;
24
+ background: rgba(0, 0, 0, 0.6);
25
+ display: flex;
26
+ justify-content: center;
27
+ align-items: center;
28
+ animation: fadeIn 1s ease-out;
29
+ }
30
+
31
+ /* Modal container */
32
+ .role-selection {
33
+ background: #fff;
34
+ padding: 40px;
35
+ border-radius: 12px;
36
+ text-align: center;
37
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
38
+ transform: translateY(-20px);
39
+ animation: slideUp 0.5s ease-out forwards;
40
+ }
41
+
42
+ /* Heading styles */
43
+ .role-selection h2 {
44
+ margin-bottom: 10px;
45
+ font-weight: 500;
46
+ color: #333;
47
+ }
48
+
49
+ .role-selection p {
50
+ margin-bottom: 20px;
51
+ color: #666;
52
+ }
53
+
54
+ /* Roles container */
55
+ .roles {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 15px;
59
+ }
60
+
61
+ /* Role buttons */
62
+ .role {
63
+ background: #007bff;
64
+ color: #fff;
65
+ border: none;
66
+ padding: 12px 20px;
67
+ border-radius: 6px;
68
+ cursor: pointer;
69
+ font-size: 16px;
70
+ transition: background 0.3s, transform 0.2s;
71
+ }
72
+
73
+ .role:hover {
74
+ background: #0056b3;
75
+ transform: translateY(-3px);
76
+ }
77
+
78
+ /* Keyframe animations */
79
+ @keyframes fadeIn {
80
+ from { opacity: 0; }
81
+ to { opacity: 1; }
82
+ }
83
+
84
+ @keyframes slideUp {
85
+ from { transform: translateY(20px); opacity: 0; }
86
+ to { transform: translateY(0); opacity: 1; }
87
+ }
src/apps/static/images/favicon.png ADDED
src/apps/static/images/judge.jpg ADDED
src/apps/static/images/lawbackground.jpg ADDED
src/apps/static/images/woman.jpeg ADDED
src/apps/static/js/entrance.js ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Dashboard Entrance Controller
3
+ * Handles the "Chime" sound, staggered arrival animations,
4
+ * and the "Security Wipe" high-tech logout transition.
5
+ */
6
+
7
+ class DashboardEntrance {
8
+ constructor() {
9
+ this.audioCtx = null;
10
+ this.initialized = false;
11
+ }
12
+
13
+ // Arrival Chime (High Crystal)
14
+ playChime() {
15
+ try {
16
+ if (!this.audioCtx) {
17
+ this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
18
+ }
19
+ if (this.audioCtx.state === 'suspended') this.audioCtx.resume();
20
+ const now = this.audioCtx.currentTime;
21
+
22
+ const osc1 = this.audioCtx.createOscillator();
23
+ const gain1 = this.audioCtx.createGain();
24
+ osc1.type = 'sine';
25
+ osc1.frequency.setValueAtTime(1320, now);
26
+ osc1.frequency.exponentialRampToValueAtTime(1567.98, now + 0.1);
27
+ gain1.gain.setValueAtTime(0, now);
28
+ gain1.gain.linearRampToValueAtTime(0.08, now + 0.05);
29
+ gain1.gain.exponentialRampToValueAtTime(0.0001, now + 0.5);
30
+ osc1.connect(gain1); gain1.connect(this.audioCtx.destination);
31
+ osc1.start(now); osc1.stop(now + 0.6);
32
+
33
+ const osc2 = this.audioCtx.createOscillator();
34
+ const gain2 = this.audioCtx.createGain();
35
+ osc2.type = 'sine';
36
+ osc2.frequency.setValueAtTime(659.25, now);
37
+ gain2.gain.setValueAtTime(0, now);
38
+ gain2.gain.linearRampToValueAtTime(0.04, now + 0.1);
39
+ gain2.gain.exponentialRampToValueAtTime(0.0001, now + 0.8);
40
+ osc2.connect(gain2); gain2.connect(this.audioCtx.destination);
41
+ osc2.start(now); osc2.stop(now + 0.8);
42
+ } catch (e) { }
43
+ }
44
+
45
+ // Logout "De-Init" Sound (Descending)
46
+ playLogoutSound() {
47
+ try {
48
+ if (!this.audioCtx) this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
49
+ if (this.audioCtx.state === 'suspended') this.audioCtx.resume();
50
+ const now = this.audioCtx.currentTime;
51
+
52
+ const osc = this.audioCtx.createOscillator();
53
+ const gain = this.audioCtx.createGain();
54
+ osc.type = 'sine';
55
+ osc.frequency.setValueAtTime(880, now); // A5
56
+ osc.frequency.exponentialRampToValueAtTime(220, now + 0.6); // A3
57
+ gain.gain.setValueAtTime(0.1, now);
58
+ gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.6);
59
+ osc.connect(gain); gain.connect(this.audioCtx.destination);
60
+ osc.start(now); osc.stop(now + 0.6);
61
+ } catch (e) { }
62
+ }
63
+
64
+ init() {
65
+ if (this.initialized) return;
66
+ this.initialized = true;
67
+
68
+ if (document.readyState === 'loading') {
69
+ document.addEventListener('DOMContentLoaded', () => this.triggerAnimations());
70
+ } else {
71
+ this.triggerAnimations();
72
+ }
73
+
74
+ const listener = () => { this.playChime(); window.removeEventListener('click', listener); };
75
+ window.addEventListener('click', listener);
76
+ setTimeout(() => this.playChime(), 100);
77
+ }
78
+
79
+ triggerAnimations() {
80
+ const sidebar = document.querySelector('.sidebar');
81
+ const header = document.querySelector('.header');
82
+ const cards = document.querySelectorAll('.card, .stat-card, .case-card, .feature-card, .case-item');
83
+ const chatbotBtn = document.querySelector('.chatbot-btn');
84
+
85
+ if (sidebar) sidebar.classList.add('animate-entrance', 'entrance-sidebar');
86
+ if (header) header.classList.add('animate-entrance', 'entrance-header');
87
+ if (chatbotBtn) chatbotBtn.classList.add('animate-entrance', 'entrance-chatbot');
88
+
89
+ cards.forEach((card, index) => {
90
+ card.classList.add('animate-entrance', 'entrance-card');
91
+ card.style.animationDelay = `${0.3 + (index * 0.08)}s`;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Professional Security Wipe Logout
97
+ */
98
+ async logout() {
99
+ // 1. Create overlay and wipe line if they don't exist
100
+ let overlay = document.querySelector('.logout-portal-overlay');
101
+ if (!overlay) {
102
+ overlay = document.createElement('div');
103
+ overlay.className = 'logout-portal-overlay';
104
+ overlay.innerHTML = `
105
+ <div class="logout-portal-message">Session Security: ACTIVE</div>
106
+ <div style="font-family: monospace; opacity: 0.5;">DE-INITIALIZING DATA PACKETS...</div>
107
+ `;
108
+ document.body.appendChild(overlay);
109
+ }
110
+
111
+ // 2. Play Sound
112
+ this.playLogoutSound();
113
+
114
+ // 3. Disassemble components
115
+ const elements = document.querySelectorAll('.sidebar, .header, .main, .cards, .card, .stat-card, .case-card, .feature-card, .chatbot-btn, .container');
116
+ elements.forEach(el => el.classList.add('exit-disassemble'));
117
+
118
+ // 4. Trigger Overlay
119
+ setTimeout(() => {
120
+ overlay.classList.add('show');
121
+ }, 100);
122
+
123
+ // 5. Cleanup and Redirect
124
+ setTimeout(() => {
125
+ localStorage.removeItem('token');
126
+ window.location.href = '/role';
127
+ }, 1200);
128
+ }
129
+ }
130
+
131
+ window.dashboardEntrance = new DashboardEntrance();
132
+ window.dashboardEntrance.init();
src/apps/static/js/roleselection.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ const rolePages = {
3
+ 'Judge': 'judgedashboard.html',
4
+ 'Advocate/Lawyer': 'advocatedashboard.html',
5
+ 'Woman': 'woman.html',
6
+ 'Citizen': 'citizen.html',
7
+ 'Student': 'studentdashboard.html',
8
+ 'Minor': 'minor.html'
9
+ };
10
+
11
+ function selectRole(role) {
12
+ if (!role || !rolePages[role]) {
13
+ console.error("❌ Invalid role selected:", role);
14
+ return;
15
+ }
16
+
17
+ console.log("✅ Role selected:", role);
18
+ // Redirect to login page with role parameter
19
+ window.location.href = `/login?role=${encodeURIComponent(role)}`;
20
+ }
21
+
22
+ document.querySelectorAll(".role").forEach(button => {
23
+ button.addEventListener("click", function () {
24
+ const role = this.getAttribute("data-role");
25
+ selectRole(role);
26
+ });
27
+ });
28
+
29
+ // Modified logout button (simple navigation)
30
+ document.getElementById("logoutBtn")?.addEventListener("click", function (e) {
31
+ e.preventDefault();
32
+ window.location.href = "/roleselection.html"; // Direct navigation back
33
+ });
34
+ });
src/apps/static/legal writing.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69e1691a2b6f8ef3cdc1793310c4a353544de60b82ca497d82a4b7bfd246183e
3
+ size 53848545
src/apps/static/video.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e48220dd2284bffd5364162287dd3041fcaaf85cc7947a06f0708583dc1206b9
3
+ size 3114915
src/apps/static/wipo_guide_ipc_2019.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa56e392f573553eefd4f0a7277190e0ff20b87949ce1c922fee802e15dc44ac
3
+ size 360729
src/apps/templates/FIR.html ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>FIR Generator - Women's Safety</title>
6
+ <style>
7
+ /* Keep existing CSS styles */
8
+ .fir-container {
9
+ max-width: 800px;
10
+ margin: 2rem auto;
11
+ font-family: 'Arial', sans-serif;
12
+ }
13
+ .form-section {
14
+ background: #fff5f7;
15
+ padding: 20px;
16
+ border-radius: 8px;
17
+ margin-bottom: 1rem;
18
+ }
19
+ label {
20
+ display: block;
21
+ margin: 10px 0 5px;
22
+ color: #c2185b;
23
+ font-weight: bold;
24
+ }
25
+ input, textarea, select {
26
+ width: 100%;
27
+ padding: 8px;
28
+ border: 1px solid #ff80ab;
29
+ border-radius: 4px;
30
+ }
31
+ .legal-warning {
32
+ color: #d32f2f;
33
+ border-left: 4px solid #d32f2f;
34
+ padding: 10px;
35
+ margin: 15px 0;
36
+ }
37
+ button {
38
+ background: #c2185b;
39
+ color: white;
40
+ padding: 12px 24px;
41
+ border: none;
42
+ border-radius: 4px;
43
+ cursor: pointer;
44
+ }
45
+ #firPreview {
46
+ white-space: pre-wrap;
47
+ font-family: 'Courier New', monospace;
48
+ background: #f8f9fa;
49
+ padding: 20px;
50
+ border-radius: 8px;
51
+ }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <!-- Keep existing HTML structure -->
56
+ <div class="fir-container">
57
+ <h1>Women's Safety FIR Generator</h1>
58
+
59
+ <div class="form-section">
60
+ <h2>Complainant Details</h2>
61
+ <label>Full Name:</label>
62
+ <input type="text" id="complainantName" required>
63
+
64
+ <label>Age:</label>
65
+ <input type="number" id="complainantAge" required>
66
+
67
+ <label>Address:</label>
68
+ <textarea id="complainantAddress" required></textarea>
69
+ </div>
70
+
71
+ <div class="form-section">
72
+ <h2>Incident Details</h2>
73
+ <label>Date & Time of Incident:</label>
74
+ <input type="datetime-local" id="incidentTime" required>
75
+
76
+ <label>Location:</label>
77
+ <input type="text" id="incidentLocation" required>
78
+
79
+ <label>Description (Include exact words used):</label>
80
+ <textarea id="incidentDescription" rows="5" required></textarea>
81
+
82
+ <label>Applicable Laws:</label>
83
+ <select id="applicableLaws" multiple>
84
+ <option value="IPC 354">IPC 354 (Outraging modesty)</option>
85
+ <option value="IPC 498A">IPC 498A (Dowry harassment)</option>
86
+ <option value="PWDVA 2005">PWDVA 2005 (Domestic Violence)</option>
87
+ <option value="IT Act 66E">IT Act 66E (Privacy violation)</option>
88
+ </select>
89
+ </div>
90
+
91
+ <div class="legal-warning">
92
+ ⚠️ Warning: False FIR filing is punishable under IPC 177 & 211
93
+ </div>
94
+
95
+ <button onclick="generateFIR()">Generate FIR Draft</button>
96
+ <button onclick="downloadDOCX()" style="display:none;" id="downloadBtn">
97
+ Download FIR (DOCX)
98
+ </button>
99
+
100
+ <div id="firPreview"></div>
101
+ </div>
102
+
103
+ <script src="https://unpkg.com/docxtemplater@latest/build/docxtemplater.js"></script>
104
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
105
+ <script>
106
+ function generateFIR() {
107
+ // Existing generateFIR code
108
+ const data = {
109
+ name: document.getElementById('complainantName').value,
110
+ age: document.getElementById('complainantAge').value,
111
+ address: document.getElementById('complainantAddress').value,
112
+ datetime: document.getElementById('incidentTime').value,
113
+ location: document.getElementById('incidentLocation').value,
114
+ description: document.getElementById('incidentDescription').value,
115
+ laws: Array.from(document.getElementById('applicableLaws').selectedOptions)
116
+ .map(option => option.value)
117
+ };
118
+
119
+ const firTemplate = `
120
+ FIR No: ______________
121
+ Police Station: ${/* Auto-fill using geolocation API */ ''}
122
+
123
+ Date: ${new Date().toLocaleDateString('en-IN')}
124
+
125
+ To,
126
+ The Station House Officer
127
+ ${/* Dynamic PS name */ '____________________ Police Station'}
128
+
129
+ Subject: FIR under ${data.laws.join(', ')} - Women's Safety Complaint
130
+
131
+ I, ${data.name}, aged ${data.age} years, residing at:
132
+ ${data.address}
133
+
134
+ solemnly declare that on ${new Date(data.datetime).toLocaleString('en-IN')} at
135
+ ${data.location}, the following incident occurred:
136
+
137
+ "${data.description}"
138
+
139
+ The incident constitutes offenses under:
140
+ ${data.laws.map(law => `- ${law}`).join('\n')}
141
+
142
+ I request that necessary action be taken under the relevant provisions of law.
143
+
144
+ Declared this ${new Date().getDate()} day of ${new Date().toLocaleString('en-IN', {month: 'long'})} ${new Date().getFullYear()}.
145
+
146
+ Signature: ____________________
147
+ Name: ${data.name}
148
+ Contact: ____________________
149
+ `;
150
+
151
+ document.getElementById('firPreview').textContent = firTemplate;
152
+ document.getElementById('downloadBtn').style.display = 'inline-block';
153
+ }
154
+
155
+ function downloadDOCX() {
156
+ const firContent = document.getElementById('firPreview').textContent;
157
+
158
+ // Create Blob with FIR content
159
+ const blob = new Blob([firContent], { type: 'text/plain' });
160
+
161
+ // Create temporary download link
162
+ const link = document.createElement('a');
163
+ link.href = URL.createObjectURL(blob);
164
+ link.download = `FIR_${Date.now()}.txt`;
165
+
166
+ // Trigger download
167
+ document.body.appendChild(link);
168
+ link.click();
169
+
170
+ // Cleanup
171
+ document.body.removeChild(link);
172
+ URL.revokeObjectURL(link.href);
173
+
174
+ // Clear all data
175
+ document.getElementById('complainantName').value = '';
176
+ document.getElementById('complainantAge').value = '';
177
+ document.getElementById('complainantAddress').value = '';
178
+ document.getElementById('incidentTime').value = '';
179
+ document.getElementById('incidentLocation').value = '';
180
+ document.getElementById('incidentDescription').value = '';
181
+ document.getElementById('applicableLaws').selectedIndex = -1;
182
+ document.getElementById('firPreview').textContent = '';
183
+ document.getElementById('downloadBtn').style.display = 'none';
184
+
185
+ // Security confirmation
186
+ alert('FIR downloaded successfully. All data has been wiped from system.');
187
+ }
188
+
189
+
190
+ </script>
191
+ </body>
192
+ </html>
src/apps/templates/Judgechatbot.html ADDED
@@ -0,0 +1,875 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Law bot</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="/static/css/modern-chat.css">
10
+ <style>
11
+ :root {
12
+ --primary-purple: #9b87f5;
13
+ --dark-purple: #1A1F2C;
14
+ --light-purple: #D6BCFA;
15
+ --neutral-gray: #8E9196;
16
+ --light-gray: #C8C8C9;
17
+ --off-white: #eee;
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ font-family: 'Inter', sans-serif;
25
+ }
26
+
27
+ body {
28
+ transition: background-color 0.3s, color 0.3s;
29
+ }
30
+
31
+ body.dark {
32
+ color: var(--off-white);
33
+ }
34
+
35
+ body.light {
36
+ color: var(--dark-purple);
37
+ }
38
+
39
+ .container {
40
+ max-width: 800px;
41
+ margin: 0 auto;
42
+ padding: 2rem;
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ .header {
49
+ display: flex;
50
+ justify-content: space-between;
51
+ align-items: center;
52
+ margin-bottom: 2rem;
53
+ }
54
+
55
+ .header-right {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 1rem;
59
+ }
60
+
61
+
62
+ .app-title {
63
+ margin-left: 40px;
64
+ font-size: 2rem;
65
+ font-weight: 600;
66
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
+ -webkit-background-clip: text;
68
+ background-clip: text;
69
+ color: transparent;
70
+ }
71
+
72
+ .theme-toggle {
73
+ background: none;
74
+ border: none;
75
+ cursor: pointer;
76
+ padding: 0.5rem;
77
+ color: var(--primary-purple);
78
+ transition: opacity 0.3s;
79
+ }
80
+
81
+ .theme-toggle:hover {
82
+ opacity: 0.8;
83
+ }
84
+
85
+ .theme-toggle svg {
86
+ width: 24px;
87
+ height: 24px;
88
+ }
89
+
90
+ .chat-container {
91
+ flex-grow: 1;
92
+ overflow-y: auto;
93
+ margin-bottom: 2rem;
94
+ padding: 1rem;
95
+ }
96
+
97
+ .message {
98
+ margin-bottom: 1.5rem;
99
+ padding: 1rem;
100
+ border-radius: 8px;
101
+ animation: fadeIn 0.3s ease-in;
102
+ }
103
+
104
+ .user-message {
105
+ background-color: rgba(155, 135, 245, 0.1);
106
+ }
107
+
108
+ .ai-message {
109
+ background-color: rgba(255, 255, 255, 0.05);
110
+ border: 1px solid rgba(200, 200, 201, 0.2);
111
+ }
112
+
113
+ body.light .user-message {
114
+ background-color: var(--off-white);
115
+ }
116
+
117
+ body.light .ai-message {
118
+ background-color: #ffffff;
119
+ border: 1px solid var(--light-gray);
120
+ }
121
+
122
+ .message-header {
123
+ display: flex;
124
+ align-items: center;
125
+ margin-bottom: 0.5rem;
126
+ font-weight: 500;
127
+ }
128
+
129
+
130
+ .sidebar-welcome {
131
+ padding: 1.5rem;
132
+ border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
+ margin-bottom: 1.5rem;
134
+ }
135
+
136
+ .user-icon,
137
+ .ai-icon {
138
+ width: 24px;
139
+ height: 24px;
140
+ border-radius: 50%;
141
+ margin-right: 0.5rem;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ font-size: 12px;
146
+ }
147
+
148
+ .user-icon {
149
+ background-color: var(--primary-purple);
150
+ color: white;
151
+ }
152
+
153
+ .ai-icon {
154
+ background-color: var(--light-purple);
155
+ color: var(--dark-purple);
156
+ }
157
+
158
+ .input-container {
159
+ position: fixed;
160
+ bottom: 0;
161
+ left: 0;
162
+ right: 0;
163
+ padding: 1rem;
164
+ background-color: var(--input-bg);
165
+ border-top: 1px solid var(--sidebar-border);
166
+ }
167
+
168
+ body.light .input-container {
169
+ background-color: #ffffff;
170
+ border-top: 1px solid var(--light-gray);
171
+ }
172
+
173
+ .input-wrapper {
174
+ max-width: 800px;
175
+ margin: 0 auto;
176
+ position: relative;
177
+ }
178
+
179
+ #messageInput {
180
+ width: 100%;
181
+ padding: 1rem;
182
+ padding-right: 3rem;
183
+ border: 1px solid rgba(200, 200, 201, 0.3);
184
+ border-radius: 8px;
185
+ font-size: 1rem;
186
+ outline: none;
187
+ transition: border-color 0.3s;
188
+ background-color: transparent;
189
+ color: inherit;
190
+ }
191
+
192
+ body.light #messageInput {
193
+ border: 1px solid var(--light-gray);
194
+ }
195
+
196
+ #messageInput:focus {
197
+ border-color: var(--primary-purple);
198
+ }
199
+
200
+ #sendButton {
201
+ position: absolute;
202
+ right: 0.5rem;
203
+ top: 50%;
204
+ transform: translateY(-50%);
205
+ background: none;
206
+ border: none;
207
+ cursor: pointer;
208
+ padding: 0.5rem;
209
+ color: var(--primary-purple);
210
+ opacity: 0.8;
211
+ transition: opacity 0.3s;
212
+ }
213
+
214
+ #sendButton:hover {
215
+ opacity: 1;
216
+ }
217
+
218
+ .typing-indicator {
219
+ display: none;
220
+ color: #888;
221
+ font-style: italic;
222
+ margin-top: 10px;
223
+ padding: 8px 12px;
224
+ background-color: #f1f1f1;
225
+ border-radius: 12px;
226
+ max-width: fit-content;
227
+ margin-left: 10px;
228
+ font-size: 14px;
229
+ animation: pulse 1.2s infinite;
230
+ }
231
+
232
+ /* Optional: soft pulse animation for effect */
233
+ @keyframes pulse {
234
+ 0% {
235
+ opacity: 0.4;
236
+ }
237
+
238
+ 50% {
239
+ opacity: 1;
240
+ }
241
+
242
+ 100% {
243
+ opacity: 0.4;
244
+ }
245
+ }
246
+
247
+ .welcome-title {
248
+ margin: 6rem 0 0.5rem 0;
249
+ text-align: center;
250
+ color: var(--neutral-gray);
251
+ padding: 1rem;
252
+ font-size: 1.24rem;
253
+ animation: fadeIn 0.3s ease-in;
254
+ }
255
+
256
+ .welcome-subtitle {
257
+ line-height: 1.6;
258
+ font-size: 1.1rem;
259
+ color: var(--neutral-gray);
260
+ max-width: 900px;
261
+ margin: 0 auto;
262
+ font-weight: 800;
263
+
264
+
265
+ }
266
+
267
+ .sidebar-welcome {
268
+ margin-bottom: 2rem;
269
+ padding: 0 1rem;
270
+ border-bottom: none;
271
+ }
272
+
273
+ .role-selection-link {
274
+ display: inline-flex;
275
+ align-items: center;
276
+ gap: initial;
277
+ color: var(--light-purple);
278
+ text-decoration: none;
279
+ margin-top: auto;
280
+ padding: auto;
281
+ transition: opacity 0.3s ease;
282
+ position: absolute;
283
+ top: 550px;
284
+ left: 30px;
285
+
286
+ }
287
+
288
+ .role-selection-link:hover {
289
+ opacity: 0.8;
290
+ }
291
+
292
+ .role-selection-link svg {
293
+ width: 16px;
294
+ height: 16px;
295
+ stroke: currentColor;
296
+ }
297
+
298
+ /* Add this media query for mobile responsiveness */
299
+ @media (max-width: 480px) {
300
+ .welcome-message p {
301
+ font-size: 1rem;
302
+ padding: 0 1rem;
303
+ }
304
+ }
305
+
306
+ @keyframes fadeIn {
307
+ from {
308
+ opacity: 0;
309
+ transform: translateY(10px);
310
+ }
311
+
312
+ to {
313
+ opacity: 1;
314
+ transform: translateY(0);
315
+ }
316
+ }
317
+
318
+ .connection-status {
319
+ position: fixed;
320
+ top: 1rem;
321
+ right: 1rem;
322
+ padding: 0.5rem 1rem;
323
+ border-radius: 4px;
324
+ font-size: 0.875rem;
325
+ display: none;
326
+ }
327
+
328
+ .connection-status.connected {
329
+ background-color: #4ade80;
330
+ color: white;
331
+ }
332
+
333
+ .connection-status.disconnected {
334
+ background-color: #ef4444;
335
+ color: white;
336
+ }
337
+
338
+ /* Styling for formatted text */
339
+ .message-content {
340
+ line-height: 1.5;
341
+ }
342
+
343
+ .message-content h1,
344
+ .message-content h2,
345
+ .message-content h3 {
346
+ margin: 1rem 0 0.5rem;
347
+ font-weight: 600;
348
+ }
349
+
350
+ .message-content h1 {
351
+ font-size: 1.5rem;
352
+ }
353
+
354
+ .message-content h2 {
355
+ font-size: 1.25rem;
356
+ }
357
+
358
+ .message-content h3 {
359
+ font-size: 1.1rem;
360
+ }
361
+
362
+ .message-content ul,
363
+ .message-content ol {
364
+ margin-left: 1.5rem;
365
+ margin-bottom: 1rem;
366
+ }
367
+
368
+ .message-content a {
369
+ color: var(--primary-purple);
370
+ text-decoration: none;
371
+ }
372
+
373
+ .message-content a:hover {
374
+ text-decoration: underline;
375
+ }
376
+
377
+ .usage-indicator {
378
+ padding: 4px 12px;
379
+ background: rgba(155, 135, 245, 0.1);
380
+ border: 1px solid rgba(155, 135, 245, 0.3);
381
+ border-radius: 20px;
382
+ font-size: 0.8rem;
383
+ color: var(--primary-purple);
384
+ font-weight: 500;
385
+ display: none;
386
+ /* Hidden by default for Admins */
387
+ }
388
+ </style>
389
+ </head>
390
+
391
+ <button class="sidebar-floating-toggle" id="floatingToggle">
392
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
393
+ <path d="M3 12h18M3 6h18M3 18h18" />
394
+ </svg>
395
+ </button>
396
+
397
+ <aside class="sidebar" id="sidebar">
398
+ <div class="sidebar-header">
399
+ <button class="new-chat-btn" id="newChatBtn">
400
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
401
+ <path d="M12 5v14M5 12h14" />
402
+ </svg>
403
+ New Chat
404
+ </button>
405
+ <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
406
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
407
+ <path d="M15 18l-6-6 6-6" />
408
+ </svg>
409
+ </button>
410
+ </div>
411
+ <div class="sidebar-title">Recent Deliberations</div>
412
+ <div class="history-list" id="historyList">
413
+ <!-- Recent prompts will appear here -->
414
+ </div>
415
+
416
+ <div class="perspective-container" style="display: none;">
417
+ <div class="sidebar-title">Answer Perspective</div>
418
+ <div class="perspective-list">
419
+ <div class="perspective-option active" data-role="Judge">⚖️ Judge</div>
420
+ </div>
421
+ </div>
422
+
423
+ <div class="sidebar-footer">
424
+ <a href="judgedashboard.html" class="role-selection-link">
425
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
426
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
427
+ </svg>
428
+ Back to Dashboard
429
+ </a>
430
+ </div>
431
+ </aside>
432
+
433
+ <!-- Existing messages will be added here dynamically -->
434
+ <div class="container">
435
+ <header class="header">
436
+ <h1 class="app-title">Law Bot (Judge)</h1>
437
+ <div class="header-right">
438
+ <div class="usage-indicator" id="usageIndicator">
439
+ Questions Remaining: <span id="remainingCount">--</span>
440
+ </div>
441
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
442
+ <!-- Icon injected by JS -->
443
+ </button>
444
+ </div>
445
+ </header>
446
+ <div class="welcome-title">Welcome, Your Honor! ⚖️</div>
447
+ <div class="welcome-subtitle">It's a privilege to support your judicial wisdom with precise legal resources.
448
+ </div>
449
+ <div class="welcome-subtitle">How may I respectfully aid your deliberations today?</div>
450
+
451
+ <div class="chat-container" id="chatContainer">
452
+ <div class="typing-indicator" id="typingIndicator">
453
+ <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--judge-color)" stroke-width="2">
454
+ <path d="M14.5 2L3.5 13L11 20.5L22 9.5L14.5 2Z" />
455
+ <path d="M7 16.5L2 21.5" />
456
+ </svg>
457
+ <span>His Honor is deliberating...</span>
458
+ </div>
459
+ </div>
460
+ <div class="input-container">
461
+ <div class="input-wrapper">
462
+ <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
463
+ <button id="sendButton">
464
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
465
+ stroke-linecap="round" stroke-linejoin="round">
466
+ <line x1="22" y1="2" x2="11" y2="13"></line>
467
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
468
+ </svg>
469
+ </button>
470
+ </div>
471
+ </div>
472
+ <footer class="professional-footer">
473
+ &copy; 2026 Law Bot AI. All Rights Reserved.
474
+ <br>
475
+ Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
476
+ </footer>
477
+ </div>
478
+ <div class="connection-status" id="connectionStatus"></div>
479
+
480
+ <script>
481
+ let ws;
482
+ const chatContainer = document.getElementById('chatContainer');
483
+ const messageInput = document.getElementById('messageInput');
484
+ const sendButton = document.getElementById('sendButton');
485
+ const typingIndicator = document.getElementById('typingIndicator');
486
+ const connectionStatus = document.getElementById('connectionStatus');
487
+ const sidebar = document.getElementById('sidebar');
488
+
489
+ let currentUserMessage = '';
490
+ let currentAiMessage = '';
491
+ let currentAiMessageElement = null;
492
+ let currentCaseId = crypto.randomUUID();
493
+ const activeRole = 'Judge'; // LOCKED ROLE
494
+ let userLimitReached = false;
495
+
496
+ async function checkUserStatus() {
497
+ const token = localStorage.getItem('token');
498
+ if (!token) return;
499
+
500
+ try {
501
+ const response = await fetch('/api/user-status', {
502
+ headers: { 'Authorization': `Bearer ${token}` }
503
+ });
504
+ const status = await response.json();
505
+
506
+ const indicator = document.getElementById('usageIndicator');
507
+ if (status.is_admin) {
508
+ indicator.style.display = 'none';
509
+ } else {
510
+ indicator.style.display = 'block';
511
+ const remaining = Math.max(0, 2 - status.question_count);
512
+ document.getElementById('remainingCount').innerText = remaining;
513
+ if (remaining <= 0) {
514
+ userLimitReached = true;
515
+ }
516
+ }
517
+ } catch (err) {
518
+ console.error('Failed to fetch user status:', err);
519
+ }
520
+ }
521
+
522
+ function formatText(text) {
523
+ // Extract references for Evidence Box
524
+ const references = [];
525
+ const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
526
+ let match;
527
+ while ((match = refRegex.exec(text)) !== null) {
528
+ references.push({ title: match[1], pages: match[2] });
529
+ }
530
+
531
+ let formatted = text
532
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
533
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
534
+ .replace(/### (.*?)\n/g, "<h3>$1</h3>")
535
+ .replace(/## (.*?)\n/g, "<h2>$1</h2>")
536
+ .replace(/# (.*?)\n/g, "<h1>$1</h1>")
537
+ .replace(/\n/g, "<br>")
538
+ .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
539
+ .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
540
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
541
+
542
+ // Add Evidence Box if references found
543
+ if (references.length > 0) {
544
+ let evidenceHtml = `<div class="evidence-box">
545
+ <div class="evidence-title">
546
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
547
+ Legal Evidence
548
+ </div>`;
549
+ references.forEach(ref => {
550
+ evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
551
+ });
552
+ evidenceHtml += `</div>`;
553
+ formatted += evidenceHtml;
554
+ }
555
+ return formatted;
556
+ }
557
+
558
+ function connectWebSocket() {
559
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
560
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
561
+
562
+ ws.onopen = () => {
563
+ console.log('Connected to WebSocket');
564
+ connectionStatus.textContent = 'Connected';
565
+ connectionStatus.className = 'connection-status connected';
566
+ connectionStatus.style.display = 'block';
567
+ setTimeout(() => {
568
+ connectionStatus.style.display = 'none';
569
+ }, 3000);
570
+ };
571
+
572
+ ws.onclose = () => {
573
+ console.log('Disconnected from WebSocket');
574
+ connectionStatus.textContent = 'Reconnecting...';
575
+ connectionStatus.className = 'connection-status disconnected';
576
+ connectionStatus.style.display = 'block';
577
+
578
+ // Exponential backoff for reconnection
579
+ const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
580
+ window._reconnectCount = (window._reconnectCount || 0) + 1;
581
+
582
+ setTimeout(() => {
583
+ reconnectWebSocket();
584
+ loadHistory();
585
+ }, backoff);
586
+ };
587
+
588
+ ws.onerror = (error) => {
589
+ console.error('WebSocket Error:', error);
590
+ };
591
+
592
+
593
+ ws.onmessage = (event) => {
594
+ console.log('Received message:', event.data);
595
+
596
+ // ✅ Hide typing indicator on response
597
+ typingIndicator.style.display = 'none';
598
+
599
+ if (event.data === '[DONE]') {
600
+ // Save complete chat interaction
601
+ saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
602
+ checkUserStatus(); // Update remaining count
603
+ return;
604
+ }
605
+
606
+ // Handle usage limit blocks from backend
607
+ if (event.data.includes("Free usage limit reached")) {
608
+ typingIndicator.style.display = 'none';
609
+ userLimitReached = true;
610
+ checkUserStatus();
611
+ }
612
+
613
+ if (!currentAiMessageElement) {
614
+ currentAiMessage = event.data;
615
+ addMessage(currentAiMessage, 'ai');
616
+ } else {
617
+ currentAiMessage += event.data;
618
+ const formattedMessage = formatText(currentAiMessage);
619
+ currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
620
+ }
621
+ };
622
+
623
+ // ✅ Scroll fix (move into sendMessage function if needed)
624
+ chatContainer.scrollTop = chatContainer.scrollHeight;
625
+ }
626
+
627
+ function reconnectWebSocket() {
628
+ console.log('Attempting to reconnect...');
629
+ connectWebSocket();
630
+ loadHistory();
631
+ }
632
+
633
+ function addMessage(content, type) {
634
+ if (type === 'user') {
635
+ currentUserMessage = content;
636
+ currentAiMessage = '';
637
+ currentAiMessageElement = null;
638
+ }
639
+
640
+ const messageDiv = document.createElement('div');
641
+ messageDiv.className = `message ${type}-message`;
642
+
643
+ const header = document.createElement('div');
644
+ header.className = 'message-header';
645
+
646
+ const icon = document.createElement('div');
647
+ icon.className = `${type}-icon`;
648
+ icon.textContent = type === 'user' ? 'U' : 'L';
649
+
650
+ const name = document.createElement('span');
651
+ name.textContent = type === 'user' ? 'You' : 'Law Bot';
652
+
653
+ header.appendChild(icon);
654
+ header.appendChild(name);
655
+
656
+ const text = document.createElement('div');
657
+ text.className = 'message-content';
658
+ text.innerHTML = type === 'ai' ? formatText(content) : content;
659
+
660
+ messageDiv.appendChild(header);
661
+ messageDiv.appendChild(text);
662
+ chatContainer.appendChild(messageDiv);
663
+ chatContainer.scrollTop = chatContainer.scrollHeight;
664
+
665
+ if (type === 'ai') {
666
+ currentAiMessageElement = messageDiv;
667
+ }
668
+ }
669
+
670
+ function sendMessage() {
671
+ const message = messageInput.value.trim();
672
+ if (!message) return;
673
+
674
+ if (userLimitReached) {
675
+ addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
676
+ return;
677
+ }
678
+
679
+ if (!activeRole) {
680
+ addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
681
+ return;
682
+ }
683
+
684
+ if (ws.readyState === WebSocket.OPEN) {
685
+ addMessage(message, 'user');
686
+ ws.send(message);
687
+ messageInput.value = '';
688
+
689
+ // ✅ Show typing indicator after sending
690
+ typingIndicator.style.display = 'block';
691
+ chatContainer.scrollTop = chatContainer.scrollHeight;
692
+ }
693
+ }
694
+ const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
695
+ if (isAtBottom) {
696
+ chatContainer.scrollTop = chatContainer.scrollHeight;
697
+ }
698
+
699
+
700
+ const historyList = document.getElementById('historyList');
701
+
702
+ async function loadHistory() {
703
+ const token = localStorage.getItem('token');
704
+ if (!token) return;
705
+
706
+ try {
707
+ const response = await fetch(`/api/interactions?role=${activeRole}`, {
708
+ headers: { 'Authorization': `Bearer ${token}` }
709
+ });
710
+ const interactions = await response.json();
711
+ historyList.innerHTML = '';
712
+ interactions.forEach(item => {
713
+ const div = document.createElement('div');
714
+ div.className = 'history-item';
715
+
716
+ // Readable preview: first sentence or truncated query
717
+ const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
718
+
719
+ div.innerHTML = `
720
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
721
+ <span>${preview}</span>
722
+ <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
723
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
724
+ </button>
725
+ `;
726
+ div.onclick = () => loadConversation(item.case_id);
727
+ historyList.appendChild(div);
728
+ });
729
+ } catch (err) {
730
+ console.error('Failed to load history:', err);
731
+ }
732
+ }
733
+
734
+ async function loadConversation(caseId) {
735
+ const token = localStorage.getItem('token');
736
+ try {
737
+ const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
738
+ headers: { 'Authorization': `Bearer ${token}` }
739
+ });
740
+ const thread = await response.json();
741
+
742
+ currentCaseId = caseId;
743
+ chatContainer.innerHTML = ''; // Clear window
744
+
745
+ thread.forEach(msg => {
746
+ addMessage(msg.query, 'user');
747
+ // Render AI Response immediately
748
+ const aiMsgDiv = document.createElement('div');
749
+ aiMsgDiv.className = 'message ai-message';
750
+ aiMsgDiv.innerHTML = `
751
+ <div class="message-header">
752
+ <div class="ai-icon">L</div>
753
+ <span>Law Bot</span>
754
+ </div>
755
+ <div class="message-content">${formatText(msg.response)}</div>
756
+ `;
757
+ chatContainer.appendChild(aiMsgDiv);
758
+ });
759
+ chatContainer.scrollTop = chatContainer.scrollHeight;
760
+ } catch (err) {
761
+ console.error('Failed to load thread:', err);
762
+ }
763
+ }
764
+
765
+ async function deleteConversation(caseId) {
766
+ const token = localStorage.getItem('token');
767
+ try {
768
+ await fetch(`/api/interactions/${caseId}`, {
769
+ method: 'DELETE',
770
+ headers: { 'Authorization': `Bearer ${token}` }
771
+ });
772
+ if (currentCaseId === caseId) {
773
+ newChat();
774
+ } else {
775
+ loadHistory();
776
+ }
777
+ } catch (err) {
778
+ console.error('Failed to delete:', err);
779
+ }
780
+ }
781
+
782
+ function newChat() {
783
+ currentCaseId = crypto.randomUUID();
784
+ chatContainer.innerHTML = '';
785
+ messageInput.value = '';
786
+ messageInput.focus();
787
+ loadHistory();
788
+ }
789
+
790
+ // ✅ Modified: Uses Bearer token
791
+ const saveChatInteraction = async (caseId, query, response) => {
792
+ const token = localStorage.getItem('token');
793
+ if (!token) return;
794
+
795
+ try {
796
+ await fetch('/api/save-interaction', {
797
+ method: 'POST',
798
+ headers: {
799
+ 'Content-Type': 'application/json',
800
+ 'Authorization': `Bearer ${token}`
801
+ },
802
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
803
+ });
804
+ loadHistory(); // Refresh sidebar history
805
+ } catch (error) {
806
+ console.error('Error saving chat:', error);
807
+ }
808
+ };
809
+
810
+ sendButton.addEventListener('click', sendMessage);
811
+ messageInput.addEventListener('keypress', (e) => {
812
+ if (e.key === 'Enter') {
813
+ sendMessage();
814
+ }
815
+ });
816
+
817
+
818
+
819
+ // Init
820
+ const themeToggle = document.getElementById('themeToggle');
821
+ const body = document.body;
822
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
823
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
824
+
825
+ function updateTheme(isDark) {
826
+ if (isDark) {
827
+ body.classList.add('dark');
828
+ body.classList.remove('light');
829
+ themeToggle.innerHTML = moonIcon;
830
+ localStorage.setItem('theme', 'dark');
831
+ } else {
832
+ body.classList.add('light');
833
+ body.classList.remove('dark');
834
+ themeToggle.innerHTML = sunIcon;
835
+ localStorage.setItem('theme', 'light');
836
+ }
837
+ }
838
+
839
+ themeToggle.addEventListener('click', () => {
840
+ const isNowDark = !body.classList.contains('dark');
841
+ updateTheme(isNowDark);
842
+ });
843
+
844
+ // Initialize Theme
845
+ const savedTheme = localStorage.getItem('theme') || 'dark';
846
+ updateTheme(savedTheme === 'dark');
847
+
848
+ // Sidebar Collapse Logic
849
+ document.getElementById('collapseSidebar').onclick = () => {
850
+ document.body.classList.add('sidebar-collapsed');
851
+ };
852
+ document.getElementById('floatingToggle').onclick = () => {
853
+ document.body.classList.remove('sidebar-collapsed');
854
+ };
855
+ document.getElementById('newChatBtn').onclick = newChat;
856
+
857
+ // Perspective Selection
858
+ document.querySelectorAll('.perspective-option').forEach(opt => {
859
+ opt.onclick = () => {
860
+ document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
861
+ opt.classList.add('active');
862
+ activeRole = opt.dataset.role;
863
+ localStorage.setItem('activeRole', activeRole);
864
+ // No session reset needed, just role update for next message
865
+ };
866
+ });
867
+
868
+ checkUserStatus();
869
+ connectWebSocket();
870
+ loadHistory();
871
+
872
+ </script>
873
+ </body>
874
+
875
+ </html>
src/apps/templates/advocatechatbot.html ADDED
@@ -0,0 +1,894 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Law bot</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="/static/css/modern-chat.css">
10
+ <style>
11
+ :root {
12
+ --primary-purple: #9b87f5;
13
+ --dark-purple: #1A1F2C;
14
+ --light-purple: #D6BCFA;
15
+ --neutral-gray: #8E9196;
16
+ --light-gray: #C8C8C9;
17
+ --off-white: #eee;
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ font-family: 'Inter', sans-serif;
25
+ }
26
+
27
+ body {
28
+ transition: background-color 0.3s, color 0.3s;
29
+ }
30
+
31
+ body.dark {
32
+ color: var(--off-white);
33
+ }
34
+
35
+ body.light {
36
+ color: var(--dark-purple);
37
+ }
38
+
39
+ .container {
40
+ max-width: 800px;
41
+ margin: 0 auto;
42
+ padding: 2rem;
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ .header {
49
+ display: flex;
50
+ justify-content: space-between;
51
+ align-items: center;
52
+ margin-bottom: 2rem;
53
+ }
54
+
55
+ .header-right {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 1rem;
59
+ }
60
+
61
+
62
+ .app-title {
63
+ margin-left: 40px;
64
+ font-size: 2rem;
65
+ font-weight: 600;
66
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
+ -webkit-background-clip: text;
68
+ background-clip: text;
69
+ color: transparent;
70
+ }
71
+
72
+ .theme-toggle {
73
+ background: none;
74
+ border: none;
75
+ cursor: pointer;
76
+ padding: 0.5rem;
77
+ color: var(--primary-purple);
78
+ transition: opacity 0.3s;
79
+ }
80
+
81
+ .theme-toggle:hover {
82
+ opacity: 0.8;
83
+ }
84
+
85
+ .theme-toggle svg {
86
+ width: 24px;
87
+ height: 24px;
88
+ }
89
+
90
+ .chat-container {
91
+ flex-grow: 1;
92
+ overflow-y: auto;
93
+ margin-bottom: 2rem;
94
+ padding: 1rem;
95
+ }
96
+
97
+ .message {
98
+ margin-bottom: 1.5rem;
99
+ padding: 1rem;
100
+ border-radius: 8px;
101
+ animation: fadeIn 0.3s ease-in;
102
+ }
103
+
104
+ .user-message {
105
+ background-color: rgba(155, 135, 245, 0.1);
106
+ }
107
+
108
+ .ai-message {
109
+ background-color: rgba(255, 255, 255, 0.05);
110
+ border: 1px solid rgba(200, 200, 201, 0.2);
111
+ }
112
+
113
+ body.light .user-message {
114
+ background-color: var(--off-white);
115
+ }
116
+
117
+ body.light .ai-message {
118
+ background-color: #ffffff;
119
+ border: 1px solid var(--light-gray);
120
+ }
121
+
122
+ .message-header {
123
+ display: flex;
124
+ align-items: center;
125
+ margin-bottom: 0.5rem;
126
+ font-weight: 500;
127
+ }
128
+
129
+
130
+ .sidebar-welcome {
131
+ padding: 1.5rem;
132
+ border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
+ margin-bottom: 1.5rem;
134
+ }
135
+
136
+ .user-icon,
137
+ .ai-icon {
138
+ width: 24px;
139
+ height: 24px;
140
+ border-radius: 50%;
141
+ margin-right: 0.5rem;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ font-size: 12px;
146
+ }
147
+
148
+ .user-icon {
149
+ background-color: var(--primary-purple);
150
+ color: white;
151
+ }
152
+
153
+ .ai-icon {
154
+ background-color: var(--light-purple);
155
+ color: var(--dark-purple);
156
+ }
157
+
158
+ .input-container {
159
+ position: fixed;
160
+ bottom: 0;
161
+ left: 0;
162
+ right: 0;
163
+ padding: 1rem;
164
+ background-color: var(--input-bg);
165
+ border-top: 1px solid var(--sidebar-border);
166
+ }
167
+
168
+ body.light .input-container {
169
+ background-color: #ffffff;
170
+ border-top: 1px solid var(--light-gray);
171
+ }
172
+
173
+ .input-wrapper {
174
+ max-width: 800px;
175
+ margin: 0 auto;
176
+ position: relative;
177
+ }
178
+
179
+ #messageInput {
180
+ width: 100%;
181
+ padding: 1rem;
182
+ padding-right: 3rem;
183
+ border: 1px solid rgba(200, 200, 201, 0.3);
184
+ border-radius: 8px;
185
+ font-size: 1rem;
186
+ outline: none;
187
+ transition: border-color 0.3s;
188
+ background-color: transparent;
189
+ color: inherit;
190
+ }
191
+
192
+ body.light #messageInput {
193
+ border: 1px solid var(--light-gray);
194
+ }
195
+
196
+ #messageInput:focus {
197
+ border-color: var(--primary-purple);
198
+ }
199
+
200
+ #sendButton {
201
+ position: absolute;
202
+ right: 0.5rem;
203
+ top: 50%;
204
+ transform: translateY(-50%);
205
+ background: none;
206
+ border: none;
207
+ cursor: pointer;
208
+ padding: 0.5rem;
209
+ color: var(--primary-purple);
210
+ opacity: 0.8;
211
+ transition: opacity 0.3s;
212
+ }
213
+
214
+ #sendButton:hover {
215
+ opacity: 1;
216
+ }
217
+
218
+ .typing-indicator {
219
+ padding: 1rem;
220
+ color: var(--neutral-gray);
221
+ font-style: italic;
222
+ display: none;
223
+ }
224
+
225
+ .welcome-title {
226
+ margin: 6rem 0 0.5rem 0;
227
+ text-align: center;
228
+ color: var(--neutral-gray);
229
+ padding: 1rem;
230
+ font-size: 1.24rem;
231
+ animation: fadeIn 0.3s ease-in;
232
+ }
233
+
234
+ .welcome-subtitle {
235
+ line-height: 1.6;
236
+ font-size: 1.24rem;
237
+ color: var(--neutral-gray);
238
+ max-width: 600px;
239
+ margin: 0 auto;
240
+ font-weight: 600;
241
+ color: var(--primary-purple);
242
+ }
243
+
244
+ .sidebar-welcome {
245
+ margin-bottom: 2rem;
246
+ padding: 0 1rem;
247
+ border-bottom: none;
248
+ }
249
+
250
+ .role-selection-link {
251
+ display: inline-flex;
252
+ align-items: center;
253
+ gap: initial;
254
+ color: var(--light-purple);
255
+ text-decoration: none;
256
+ margin-top: auto;
257
+ padding: auto;
258
+ transition: opacity 0.3s ease;
259
+ position: absolute;
260
+ top: 550px;
261
+ left: 30px;
262
+
263
+ }
264
+
265
+ .role-selection-link:hover {
266
+ opacity: 0.8;
267
+ }
268
+
269
+ .role-selection-link svg {
270
+ width: 16px;
271
+ height: 16px;
272
+ stroke: currentColor;
273
+ }
274
+
275
+ /* Add this media query for mobile responsiveness */
276
+ @media (max-width: 480px) {
277
+ .welcome-message p {
278
+ font-size: 1rem;
279
+ padding: 0 1rem;
280
+ }
281
+ }
282
+
283
+ @keyframes fadeIn {
284
+ from {
285
+ opacity: 0;
286
+ transform: translateY(10px);
287
+ }
288
+
289
+ to {
290
+ opacity: 1;
291
+ transform: translateY(0);
292
+ }
293
+ }
294
+
295
+ .connection-status {
296
+ position: fixed;
297
+ top: 1rem;
298
+ right: 1rem;
299
+ padding: 0.5rem 1rem;
300
+ border-radius: 4px;
301
+ font-size: 0.875rem;
302
+ display: none;
303
+ }
304
+
305
+ .connection-status.connected {
306
+ background-color: #4ade80;
307
+ color: white;
308
+ }
309
+
310
+ .connection-status.disconnected {
311
+ background-color: #ef4444;
312
+ color: white;
313
+ }
314
+
315
+ /* Styling for formatted text */
316
+ .message-content {
317
+ line-height: 1.5;
318
+ }
319
+
320
+ .message-content h1,
321
+ .message-content h2,
322
+ .message-content h3 {
323
+ margin: 1rem 0 0.5rem;
324
+ font-weight: 600;
325
+ }
326
+
327
+ .message-content h1 {
328
+ font-size: 1.5rem;
329
+ }
330
+
331
+ .message-content h2 {
332
+ font-size: 1.25rem;
333
+ }
334
+
335
+ .message-content h3 {
336
+ font-size: 1.1rem;
337
+ }
338
+
339
+ .message-content ul,
340
+ .message-content ol {
341
+ margin-left: 1.5rem;
342
+ margin-bottom: 1rem;
343
+ }
344
+
345
+ .message-content a {
346
+ color: var(--primary-purple);
347
+ text-decoration: none;
348
+ }
349
+
350
+ .message-content a:hover {
351
+ text-decoration: underline;
352
+ }
353
+
354
+ .usage-indicator {
355
+ padding: 4px 12px;
356
+ background: rgba(155, 135, 245, 0.1);
357
+ border: 1px solid rgba(155, 135, 245, 0.3);
358
+ border-radius: 20px;
359
+ font-size: 0.8rem;
360
+ color: var(--primary-purple);
361
+ font-weight: 500;
362
+ display: none;
363
+ /* Hidden by default for Admins */
364
+ }
365
+ </style>
366
+ </head>
367
+
368
+ <button class="sidebar-floating-toggle" id="floatingToggle">
369
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
370
+ <path d="M3 12h18M3 6h18M3 18h18" />
371
+ </svg>
372
+ </button>
373
+
374
+ <aside class="sidebar" id="sidebar">
375
+ <div class="sidebar-header">
376
+ <button class="new-chat-btn" id="newChatBtn">
377
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378
+ <path d="M12 5v14M5 12h14" />
379
+ </svg>
380
+ New Chat
381
+ </button>
382
+ <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
383
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
384
+ <path d="M15 18l-6-6 6-6" />
385
+ </svg>
386
+ </button>
387
+ </div>
388
+ <div class="sidebar-title">Recent Cases</div>
389
+ <div class="history-list" id="historyList">
390
+ <!-- Recent prompts will appear here -->
391
+ </div>
392
+
393
+ <div class="perspective-container" style="display: none;">
394
+ <div class="sidebar-title">Answer Perspective</div>
395
+ <div class="perspective-list">
396
+ <div class="perspective-option active" data-role="Advocate">🛡️ Advocate</div>
397
+ </div>
398
+ </div>
399
+
400
+ <div class="sidebar-footer">
401
+ <a href="/advocatedashboard.html" class="role-selection-link">
402
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
403
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
404
+ </svg>
405
+ Back to Dashboard
406
+ </a>
407
+ </div>
408
+ </aside>
409
+
410
+ <!-- Existing messages will be added here dynamically -->
411
+ <div class="container">
412
+ <header class="header">
413
+ <h1 class="app-title">Law Bot (Advocate)</h1>
414
+ <div class="header-right">
415
+ <div class="usage-indicator" id="usageIndicator">
416
+ Questions Remaining: <span id="remainingCount">--</span>
417
+ </div>
418
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
419
+ <!-- Icon injected by JS -->
420
+ </button>
421
+ <div class="user-profile-menu" id="userProfileMenu">
422
+ <div class="profile-icon-btn">
423
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
424
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
425
+ <circle cx="12" cy="7" r="4"></circle>
426
+ </svg>
427
+ </div>
428
+ <div class="dropdown-menu" id="dropdownMenu">
429
+ <div class="dropdown-item role-info">
430
+ Role: Advocate
431
+ </div>
432
+ <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
433
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
434
+ stroke-width="2">
435
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
436
+ <polyline points="16 17 21 12 16 7"></polyline>
437
+ <line x1="21" y1="12" x2="9" y2="12"></line>
438
+ </svg>
439
+ Logout
440
+ </a>
441
+ </div>
442
+ </div>
443
+ </div>
444
+ </header>
445
+ <div class="welcome-title">Welcome, Advocate! 🎓</div>
446
+ <div class="welcome-subtitle">How can I help you with legal references and case laws?</div>
447
+
448
+
449
+ <div class="chat-container" id="chatContainer">
450
+ <div class="typing-indicator" id="typingIndicator">
451
+ <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--advocate-color)" stroke-width="2">
452
+ <circle cx="12" cy="12" r="10" />
453
+ <line x1="8" y1="12" x2="16" y2="12" />
454
+ <line x1="12" y1="8" x2="12" y2="16" />
455
+ </svg>
456
+ <span>Analyzing case law...</span>
457
+ </div>
458
+ </div>
459
+ <div class="input-container">
460
+ <div class="input-wrapper">
461
+ <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
462
+ <button id="sendButton">
463
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
464
+ stroke-linecap="round" stroke-linejoin="round">
465
+ <line x1="22" y1="2" x2="11" y2="13"></line>
466
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
467
+ </svg>
468
+ </button>
469
+ </div>
470
+ </div>
471
+ <footer class="professional-footer">
472
+ &copy; 2026 Law Bot AI. All Rights Reserved.
473
+ <br>
474
+ Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
475
+ </footer>
476
+ </div>
477
+ <div class="connection-status" id="connectionStatus"></div>
478
+
479
+ <script>
480
+ let ws;
481
+ const chatContainer = document.getElementById('chatContainer');
482
+ const messageInput = document.getElementById('messageInput');
483
+ const sendButton = document.getElementById('sendButton');
484
+ const typingIndicator = document.getElementById('typingIndicator');
485
+ const connectionStatus = document.getElementById('connectionStatus');
486
+ const sidebar = document.getElementById('sidebar');
487
+
488
+ let currentUserMessage = '';
489
+ let currentAiMessage = '';
490
+ let currentAiMessageElement = null;
491
+ let currentCaseId = crypto.randomUUID();
492
+ const activeRole = 'Advocate'; // LOCKED ROLE
493
+ let userLimitReached = false;
494
+
495
+ async function checkUserStatus() {
496
+ const token = localStorage.getItem('token');
497
+ if (!token) return;
498
+
499
+ try {
500
+ const response = await fetch('/api/user-status', {
501
+ headers: { 'Authorization': `Bearer ${token}` }
502
+ });
503
+ const status = await response.json();
504
+
505
+ const indicator = document.getElementById('usageIndicator');
506
+ if (status.is_admin) {
507
+ indicator.style.display = 'none';
508
+ } else {
509
+ indicator.style.display = 'block';
510
+ const remaining = Math.max(0, 2 - status.question_count);
511
+ document.getElementById('remainingCount').innerText = remaining;
512
+ if (remaining <= 0) {
513
+ userLimitReached = true;
514
+ }
515
+ }
516
+ } catch (err) {
517
+ console.error('Failed to fetch user status:', err);
518
+ }
519
+ }
520
+
521
+ function formatText(text) {
522
+ // Extract references for Evidence Box
523
+ const references = [];
524
+ const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
525
+ let match;
526
+ while ((match = refRegex.exec(text)) !== null) {
527
+ references.push({ title: match[1], pages: match[2] });
528
+ }
529
+
530
+ let formatted = text
531
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
532
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
533
+ .replace(/### (.*?)\n/g, "<h3>$1</h3>")
534
+ .replace(/## (.*?)\n/g, "<h2>$1</h2>")
535
+ .replace(/# (.*?)\n/g, "<h1>$1</h1>")
536
+ .replace(/\n/g, "<br>")
537
+ .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
538
+ .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
539
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
540
+
541
+ // Add Evidence Box if references found
542
+ if (references.length > 0) {
543
+ let evidenceHtml = `<div class="evidence-box">
544
+ <div class="evidence-title">
545
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
546
+ Legal Evidence
547
+ </div>`;
548
+ references.forEach(ref => {
549
+ evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
550
+ });
551
+ evidenceHtml += `</div>`;
552
+ formatted += evidenceHtml;
553
+ }
554
+ return formatted;
555
+ }
556
+
557
+ function connectWebSocket() {
558
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
559
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
560
+
561
+ ws.onopen = () => {
562
+ console.log('Connected to WebSocket');
563
+ connectionStatus.textContent = 'Connected';
564
+ connectionStatus.className = 'connection-status connected';
565
+ connectionStatus.style.display = 'block';
566
+ setTimeout(() => {
567
+ connectionStatus.style.display = 'none';
568
+ }, 3000);
569
+ };
570
+
571
+ ws.onclose = () => {
572
+ console.log('Disconnected from WebSocket');
573
+ connectionStatus.textContent = 'Reconnecting...';
574
+ connectionStatus.className = 'connection-status disconnected';
575
+ connectionStatus.style.display = 'block';
576
+
577
+ // Exponential backoff for reconnection
578
+ const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
579
+ window._reconnectCount = (window._reconnectCount || 0) + 1;
580
+
581
+ setTimeout(() => {
582
+ reconnectWebSocket();
583
+ loadHistory();
584
+ }, backoff);
585
+ };
586
+
587
+ ws.onerror = (error) => {
588
+ console.error('WebSocket Error:', error);
589
+ };
590
+
591
+
592
+ ws.onmessage = (event) => {
593
+ console.log('Received message:', event.data);
594
+
595
+ // ✅ Hide typing indicator on response
596
+ typingIndicator.style.display = 'none';
597
+
598
+ if (event.data === '[DONE]') {
599
+ // Save complete chat interaction
600
+ saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
601
+ checkUserStatus(); // Update remaining count
602
+ return;
603
+ }
604
+
605
+ // Handle usage limit blocks from backend
606
+ if (event.data.includes("Free usage limit reached")) {
607
+ typingIndicator.style.display = 'none';
608
+ userLimitReached = true;
609
+ checkUserStatus();
610
+ }
611
+
612
+ if (!currentAiMessageElement) {
613
+ currentAiMessage = event.data;
614
+ addMessage(currentAiMessage, 'ai');
615
+ } else {
616
+ currentAiMessage += event.data;
617
+ const formattedMessage = formatText(currentAiMessage);
618
+ currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
619
+ }
620
+ };
621
+
622
+ // ✅ Scroll fix (move into sendMessage function if needed)
623
+ chatContainer.scrollTop = chatContainer.scrollHeight;
624
+ }
625
+
626
+ function reconnectWebSocket() {
627
+ console.log('Attempting to reconnect...');
628
+ connectWebSocket();
629
+ loadHistory();
630
+ }
631
+
632
+ function addMessage(content, type) {
633
+ if (type === 'user') {
634
+ currentUserMessage = content;
635
+ currentAiMessage = '';
636
+ currentAiMessageElement = null;
637
+ }
638
+
639
+ const messageDiv = document.createElement('div');
640
+ messageDiv.className = `message ${type}-message`;
641
+
642
+ const header = document.createElement('div');
643
+ header.className = 'message-header';
644
+
645
+ const icon = document.createElement('div');
646
+ icon.className = `${type}-icon`;
647
+ icon.textContent = type === 'user' ? 'U' : 'L';
648
+
649
+ const name = document.createElement('span');
650
+ name.textContent = type === 'user' ? 'You' : 'Law Bot';
651
+
652
+ header.appendChild(icon);
653
+ header.appendChild(name);
654
+
655
+ const text = document.createElement('div');
656
+ text.className = 'message-content';
657
+ text.innerHTML = type === 'ai' ? formatText(content) : content;
658
+
659
+ messageDiv.appendChild(header);
660
+ messageDiv.appendChild(text);
661
+ chatContainer.appendChild(messageDiv);
662
+ chatContainer.scrollTop = chatContainer.scrollHeight;
663
+
664
+ if (type === 'ai') {
665
+ currentAiMessageElement = messageDiv;
666
+ }
667
+ }
668
+
669
+ function sendMessage() {
670
+ const message = messageInput.value.trim();
671
+ if (!message) return;
672
+
673
+ if (userLimitReached) {
674
+ addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
675
+ return;
676
+ }
677
+
678
+ if (!activeRole) {
679
+ addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
680
+ return;
681
+ }
682
+
683
+ if (ws.readyState === WebSocket.OPEN) {
684
+ addMessage(message, 'user');
685
+ ws.send(message);
686
+ messageInput.value = '';
687
+
688
+ // ✅ Show typing indicator after sending
689
+ typingIndicator.style.display = 'block';
690
+ chatContainer.scrollTop = chatContainer.scrollHeight;
691
+ }
692
+ }
693
+ const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
694
+ if (isAtBottom) {
695
+ chatContainer.scrollTop = chatContainer.scrollHeight;
696
+ }
697
+
698
+
699
+ const historyList = document.getElementById('historyList');
700
+
701
+ async function loadHistory() {
702
+ const token = localStorage.getItem('token');
703
+ if (!token) return;
704
+
705
+ try {
706
+ const response = await fetch(`/api/interactions?role=${activeRole}`, {
707
+ headers: { 'Authorization': `Bearer ${token}` }
708
+ });
709
+ const interactions = await response.json();
710
+ historyList.innerHTML = '';
711
+ interactions.forEach(item => {
712
+ const div = document.createElement('div');
713
+ div.className = 'history-item';
714
+
715
+ // Readable preview: first sentence or truncated query
716
+ const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
717
+
718
+ div.innerHTML = `
719
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
720
+ <span>${preview}</span>
721
+ <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
722
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
723
+ </button>
724
+ `;
725
+ div.onclick = () => loadConversation(item.case_id);
726
+ historyList.appendChild(div);
727
+ });
728
+ } catch (err) {
729
+ console.error('Failed to load history:', err);
730
+ }
731
+ }
732
+
733
+ async function loadConversation(caseId) {
734
+ const token = localStorage.getItem('token');
735
+ try {
736
+ const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
737
+ headers: { 'Authorization': `Bearer ${token}` }
738
+ });
739
+ const thread = await response.json();
740
+
741
+ currentCaseId = caseId;
742
+ chatContainer.innerHTML = ''; // Clear window
743
+
744
+ thread.forEach(msg => {
745
+ addMessage(msg.query, 'user');
746
+ // Render AI Response immediately
747
+ const aiMsgDiv = document.createElement('div');
748
+ aiMsgDiv.className = 'message ai-message';
749
+ aiMsgDiv.innerHTML = `
750
+ <div class="message-header">
751
+ <div class="ai-icon">L</div>
752
+ <span>Law Bot</span>
753
+ </div>
754
+ <div class="message-content">${formatText(msg.response)}</div>
755
+ `;
756
+ chatContainer.appendChild(aiMsgDiv);
757
+ });
758
+ chatContainer.scrollTop = chatContainer.scrollHeight;
759
+ } catch (err) {
760
+ console.error('Failed to load thread:', err);
761
+ }
762
+ }
763
+
764
+ async function deleteConversation(caseId) {
765
+ const token = localStorage.getItem('token');
766
+ try {
767
+ await fetch(`/api/interactions/${caseId}`, {
768
+ method: 'DELETE',
769
+ headers: { 'Authorization': `Bearer ${token}` }
770
+ });
771
+ if (currentCaseId === caseId) {
772
+ newChat();
773
+ } else {
774
+ loadHistory();
775
+ }
776
+ } catch (err) {
777
+ console.error('Failed to delete:', err);
778
+ }
779
+ }
780
+
781
+ function newChat() {
782
+ currentCaseId = crypto.randomUUID();
783
+ chatContainer.innerHTML = '';
784
+ messageInput.value = '';
785
+ messageInput.focus();
786
+ loadHistory();
787
+ }
788
+
789
+ // ✅ Modified: Uses Bearer token
790
+ const saveChatInteraction = async (caseId, query, response) => {
791
+ const token = localStorage.getItem('token');
792
+ if (!token) return;
793
+
794
+ try {
795
+ await fetch('/api/save-interaction', {
796
+ method: 'POST',
797
+ headers: {
798
+ 'Content-Type': 'application/json',
799
+ 'Authorization': `Bearer ${token}`
800
+ },
801
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
802
+ });
803
+ loadHistory(); // Refresh sidebar history
804
+ } catch (error) {
805
+ console.error('Error saving chat:', error);
806
+ }
807
+ };
808
+
809
+ sendButton.addEventListener('click', sendMessage);
810
+ messageInput.addEventListener('keypress', (e) => {
811
+ if (e.key === 'Enter') {
812
+ sendMessage();
813
+ }
814
+ });
815
+
816
+ // Theme toggle
817
+ // --- User Profile Dropdown ---
818
+ const userProfileMenu = document.getElementById('userProfileMenu');
819
+ const dropdownMenu = document.getElementById('dropdownMenu');
820
+ const headerLogoutBtn = document.getElementById('headerLogoutBtn');
821
+
822
+ userProfileMenu.addEventListener('click', (e) => {
823
+ e.stopPropagation();
824
+ dropdownMenu.classList.toggle('show');
825
+ });
826
+
827
+ document.addEventListener('click', () => {
828
+ if (dropdownMenu.classList.contains('show')) {
829
+ dropdownMenu.classList.remove('show');
830
+ }
831
+ });
832
+
833
+ headerLogoutBtn.addEventListener('click', (e) => {
834
+ e.preventDefault();
835
+ localStorage.removeItem('token');
836
+ window.location.href = '/role';
837
+ });
838
+
839
+ const themeToggle = document.getElementById('themeToggle');
840
+ const body = document.body;
841
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
842
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
843
+
844
+ function updateTheme(isDark) {
845
+ if (isDark) {
846
+ body.classList.add('dark');
847
+ body.classList.remove('light');
848
+ themeToggle.innerHTML = moonIcon;
849
+ localStorage.setItem('theme', 'dark');
850
+ } else {
851
+ body.classList.add('light');
852
+ body.classList.remove('dark');
853
+ themeToggle.innerHTML = sunIcon;
854
+ localStorage.setItem('theme', 'light');
855
+ }
856
+ }
857
+
858
+ themeToggle.addEventListener('click', () => {
859
+ const isNowDark = !body.classList.contains('dark');
860
+ updateTheme(isNowDark);
861
+ });
862
+
863
+ // Initialize Theme
864
+ const savedTheme = localStorage.getItem('theme') || 'dark';
865
+ updateTheme(savedTheme === 'dark');
866
+
867
+ // Sidebar Collapse Logic
868
+ document.getElementById('collapseSidebar').onclick = () => {
869
+ document.body.classList.add('sidebar-collapsed');
870
+ };
871
+ document.getElementById('floatingToggle').onclick = () => {
872
+ document.body.classList.remove('sidebar-collapsed');
873
+ };
874
+ document.getElementById('newChatBtn').onclick = newChat;
875
+
876
+ // Perspective Selection
877
+ document.querySelectorAll('.perspective-option').forEach(opt => {
878
+ opt.onclick = () => {
879
+ document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
880
+ opt.classList.add('active');
881
+ activeRole = opt.dataset.role;
882
+ localStorage.setItem('activeRole', activeRole);
883
+ // No session reset needed, just role update for next message
884
+ };
885
+ });
886
+
887
+ checkUserStatus();
888
+ connectWebSocket();
889
+ loadHistory();
890
+
891
+ </script>
892
+ </body>
893
+
894
+ </html>
src/apps/templates/advocatedashboard.html ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Advocate Dashboard</title>
8
+ <link rel="stylesheet" href="/static/css/entrance.css">
9
+ <script src="/static/js/entrance.js" defer></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ :root {
13
+ --primary: #2a5298;
14
+ --secondary: #1e3c72;
15
+ --accent: #e67e22;
16
+ }
17
+
18
+ body {
19
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20
+ margin: 0;
21
+ background: #f5f7fa;
22
+ }
23
+
24
+ .header {
25
+ background: linear-gradient(135deg, var(--secondary), var(--primary));
26
+ color: white;
27
+ padding: 1rem 2rem;
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ position: relative;
32
+ z-index: 1000;
33
+ }
34
+
35
+ .profile {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 10px;
39
+ }
40
+
41
+ .profile img {
42
+ width: 40px;
43
+ height: 40px;
44
+ border-radius: 50%;
45
+ border: 2px solid rgba(255, 255, 255, 0.3);
46
+ }
47
+
48
+ .container {
49
+ display: grid;
50
+ grid-template-columns: 250px 1fr;
51
+ min-height: calc(100vh - 70px);
52
+ }
53
+
54
+ .sidebar {
55
+ background: white;
56
+ padding: 1.5rem;
57
+ box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
58
+ }
59
+
60
+ .sidebar a {
61
+ display: block;
62
+ padding: 10px;
63
+ color: #555;
64
+ text-decoration: none;
65
+ border-radius: 5px;
66
+ margin-bottom: 5px;
67
+ }
68
+
69
+ .sidebar a:hover,
70
+ .sidebar a.active {
71
+ background: #e6f0ff;
72
+ color: var(--primary);
73
+ }
74
+
75
+ .main-content {
76
+ padding: 2rem;
77
+ }
78
+
79
+ .card {
80
+ background: white;
81
+ border-radius: 8px;
82
+ padding: 1.5rem;
83
+ margin-bottom: 1.5rem;
84
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
85
+ }
86
+
87
+ .stats {
88
+ display: grid;
89
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
90
+ gap: 1rem;
91
+ }
92
+
93
+ .stat-item {
94
+ text-align: center;
95
+ padding: 1rem;
96
+ border-radius: 8px;
97
+ background: #f8f9fa;
98
+ }
99
+
100
+ .stat-item h3 {
101
+ color: var(--primary);
102
+ margin: 0;
103
+ }
104
+
105
+ .case-list {
106
+ display: grid;
107
+ gap: 1rem;
108
+ }
109
+
110
+ .case-item {
111
+ padding: 1rem;
112
+ border-left: 4px solid var(--accent);
113
+ background: white;
114
+ border-radius: 4px;
115
+ }
116
+
117
+ .case-item h4 {
118
+ margin: 0;
119
+ color: var(--secondary);
120
+ }
121
+
122
+ .case-meta {
123
+ display: flex;
124
+ gap: 1rem;
125
+ color: #666;
126
+ font-size: 0.9rem;
127
+ }
128
+
129
+ .resources-grid {
130
+ display: grid;
131
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
132
+ gap: 1rem;
133
+ }
134
+
135
+ .resource-card {
136
+ border: 1px solid #ddd;
137
+ border-radius: 8px;
138
+ padding: 1rem;
139
+ transition: all 0.3s;
140
+ }
141
+
142
+ .resource-card:hover {
143
+ border-color: var(--primary);
144
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
145
+ }
146
+
147
+ /* Chatbot button */
148
+ .chatbot-btn {
149
+ position: fixed;
150
+ bottom: 2rem;
151
+ right: 2rem;
152
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
153
+ color: white;
154
+ width: 60px;
155
+ height: 60px;
156
+ border-radius: 50%;
157
+ display: flex;
158
+ justify-content: center;
159
+ align-items: center;
160
+ box-shadow: 0 4px 20px rgba(30, 60, 114, 0.3);
161
+ cursor: pointer;
162
+ transition: all 0.3s ease;
163
+ z-index: 90;
164
+ }
165
+
166
+ .chatbot-btn:hover {
167
+ transform: scale(1.1);
168
+ box-shadow: 0 6px 25px rgba(30, 60, 114, 0.4);
169
+ }
170
+
171
+ .chatbot-btn i {
172
+ font-size: 1.5rem;
173
+ }
174
+
175
+ /* --- USER DROPDOWN --- */
176
+ .user-profile-menu {
177
+ position: relative;
178
+ cursor: pointer;
179
+ }
180
+
181
+ .profile {
182
+ cursor: pointer;
183
+ transition: opacity 0.3s;
184
+ }
185
+
186
+ .profile:hover {
187
+ opacity: 0.9;
188
+ }
189
+
190
+ .dropdown-menu {
191
+ position: absolute;
192
+ top: 100%;
193
+ right: 0;
194
+ width: 220px;
195
+ background: white;
196
+ border-radius: 8px;
197
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
198
+ display: none;
199
+ flex-direction: column !important;
200
+ overflow: hidden;
201
+ z-index: 2000;
202
+ margin-top: 10px;
203
+ border: 1px solid #e0e4e8;
204
+ }
205
+
206
+ .dropdown-menu.show {
207
+ display: flex !important;
208
+ animation: fadeIn 0.2s ease-out;
209
+ }
210
+
211
+ @keyframes fadeIn {
212
+ from {
213
+ opacity: 0;
214
+ transform: translateY(10px);
215
+ }
216
+
217
+ to {
218
+ opacity: 1;
219
+ transform: translateY(0);
220
+ }
221
+ }
222
+
223
+ .dropdown-item {
224
+ padding: 1rem;
225
+ color: #333;
226
+ text-decoration: none;
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 0.5rem;
230
+ transition: background 0.2s;
231
+ border-bottom: 1px solid #f0f0f0;
232
+ }
233
+
234
+ .dropdown-item:last-child {
235
+ border-bottom: none;
236
+ }
237
+
238
+ .dropdown-item:hover {
239
+ background: #f8f9fa;
240
+ color: #2a5298;
241
+ }
242
+
243
+ .dropdown-item.role-info {
244
+ background: #f1f4f8;
245
+ font-weight: 500;
246
+ color: #4a5568;
247
+ cursor: default;
248
+ }
249
+
250
+ .dropdown-item.usage-info {
251
+ background: #fff5f5;
252
+ font-size: 0.85rem;
253
+ color: #e53e3e;
254
+ font-weight: 600;
255
+ display: none;
256
+ }
257
+
258
+ .logout-action {
259
+ color: #e53e3e !important;
260
+ font-weight: 500;
261
+ }
262
+
263
+ .logout-action i {
264
+ margin-right: 8px;
265
+ }
266
+
267
+ .logout-action:hover {
268
+ background: #fff5f5;
269
+ }
270
+ </style>
271
+ </head>
272
+
273
+ <body>
274
+ <header class="header">
275
+ <h1>Advocate Dashboard</h1>
276
+ <div class="user-profile-menu" id="userProfileMenu">
277
+ <div class="profile">
278
+ <span>Adv. Rahul Sharma</span>
279
+ <img src="https://randomuser.me/api/portraits/men/32.jpg" alt="Profile">
280
+ <i class="fas fa-chevron-down" style="font-size: 0.8rem; margin-left: 10px; opacity: 0.8;"></i>
281
+ </div>
282
+ <div class="dropdown-menu" id="dropdownMenu">
283
+ <div class="dropdown-item role-info">
284
+ Role: Advocate
285
+ </div>
286
+ <div class="dropdown-item usage-info" id="usageInfo">
287
+ Questions Remaining: <span id="remainingCount">--</span>
288
+ </div>
289
+ <div class="dropdown-item logout-action" id="headerLogoutBtn" style="cursor: pointer;">
290
+ <i class="fas fa-sign-out-alt"></i> Logout
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </header>
295
+
296
+ <div class="container">
297
+ <aside class="sidebar">
298
+ <a href="advocatedashboard.html" class="active"><i class="fas fa-home"></i> Dashboard</a>
299
+ <a href="advocateresources.html" id="resources"><i class="fas fa-book"></i> Legal Resources</a>
300
+
301
+ </aside>
302
+
303
+ <main class="main-content">
304
+ <div class="card">
305
+ <h2>Today's Overview</h2>
306
+ <div class="stats">
307
+ <div class="stat-item">
308
+ <h3>12</h3>
309
+ <p>Active Cases</p>
310
+ </div>
311
+ <div class="stat-item">
312
+ <h3>3</h3>
313
+ <p>Today's Hearings</p>
314
+ </div>
315
+ <div class="stat-item">
316
+ <h3>5</h3>
317
+ <p>Pending Filings</p>
318
+ </div>
319
+ <div class="stat-item">
320
+ <h3>2</h3>
321
+ <p>Urgent Matters</p>
322
+ </div>
323
+ </div>
324
+ </div>
325
+
326
+ <div class="card">
327
+ <h2>Upcoming Hearings</h2>
328
+ <div class="case-list">
329
+ <div class="case-item">
330
+ <h4>State vs. Kumar - Bail Hearing</h4>
331
+ <div class="case-meta">
332
+ <span><i class="fas fa-hashtag"></i> CR-2024-0785</span>
333
+ <span><i class="fas fa-clock"></i> Today, 11:30 AM</span>
334
+ <span><i class="fas fa-map-marker-alt"></i> Courtroom 3</span>
335
+ </div>
336
+ </div>
337
+ <div class="case-item">
338
+ <h4>M/s. ABC Corp vs. XYZ Ltd</h4>
339
+ <div class="case-meta">
340
+ <span><i class="fas fa-hashtag"></i> CS-2024-0456</span>
341
+ <span><i class="fas fa-clock"></i> Tomorrow, 2:15 PM</span>
342
+ <span><i class="fas fa-map-marker-alt"></i> Courtroom 5</span>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ <div class="card">
349
+ <h2>Recent Case Updates</h2>
350
+ <div class="case-list">
351
+ <div class="case-item">
352
+ <h4>Patel vs. State - Judgment Reserved</h4>
353
+ <div class="case-meta">
354
+ <span><i class="fas fa-hashtag"></i> CR-2024-0123</span>
355
+ <span>Hon'ble Justice Verma</span>
356
+ </div>
357
+ <p>Final arguments completed. Judgment expected within 15 days.</p>
358
+ </div>
359
+ </div>
360
+ </div>
361
+ </main>
362
+ </div>
363
+ <!-- Chatbot Button -->
364
+ <div class="chatbot-btn" id="chatbotButton">
365
+ <i class="fas fa-robot"></i>
366
+ </div>
367
+
368
+ <script>
369
+ // --- User Profile Dropdown Logic ---
370
+ const userProfileMenu = document.getElementById('userProfileMenu');
371
+ const dropdownMenu = document.getElementById('dropdownMenu');
372
+ const sidebarLogoutBtn = document.getElementById('logoutBtn');
373
+ const headerLogoutBtn = document.getElementById('headerLogoutBtn');
374
+
375
+ async function checkUserStatus() {
376
+ const token = localStorage.getItem('token');
377
+ if (!token) return;
378
+
379
+ try {
380
+ const response = await fetch('/api/user-status', {
381
+ headers: { 'Authorization': `Bearer ${token}` }
382
+ });
383
+ const status = await response.json();
384
+
385
+ const usageInfo = document.getElementById('usageInfo');
386
+ if (status.is_admin) {
387
+ usageInfo.style.display = 'none';
388
+ } else {
389
+ usageInfo.style.display = 'flex';
390
+ const remaining = Math.max(0, 2 - status.question_count);
391
+ document.getElementById('remainingCount').innerText = remaining;
392
+ }
393
+ } catch (err) {
394
+ console.error('Failed to fetch user status:', err);
395
+ }
396
+ }
397
+
398
+ checkUserStatus();
399
+
400
+ userProfileMenu.addEventListener('click', (e) => {
401
+ e.stopPropagation();
402
+ dropdownMenu.classList.toggle('show');
403
+ });
404
+
405
+ document.addEventListener('click', () => {
406
+ if (dropdownMenu.classList.contains('show')) {
407
+ dropdownMenu.classList.remove('show');
408
+ }
409
+ });
410
+
411
+ function performLogout() {
412
+ if (window.dashboardEntrance) {
413
+ window.dashboardEntrance.logout();
414
+ } else {
415
+ localStorage.removeItem('token');
416
+ window.location.href = "/role";
417
+ }
418
+ }
419
+
420
+ headerLogoutBtn.addEventListener('click', performLogout);
421
+
422
+ if (sidebarLogoutBtn) {
423
+ sidebarLogoutBtn.addEventListener("click", function (e) {
424
+ e.preventDefault();
425
+ performLogout();
426
+ });
427
+ }
428
+
429
+ document.getElementById('resources').addEventListener('click', function () {
430
+ window.location.href = 'advocateresources.html';
431
+ });
432
+ document.getElementById("chatbotButton").addEventListener("click", function () {
433
+ // Here you would typically open a chatbot modal or redirect to a chatbot page
434
+ alert("Advocate Assistant Chatbot is opening...");
435
+ window.location.href = "/advocatechatbot.html";
436
+ });
437
+ </script>
438
+ </body>
439
+
440
+ </html>
src/apps/templates/advocateresources.html ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Advocate Resources</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary: #2a5298;
11
+ --secondary: #1e3c72;
12
+ --accent: #e67e22;
13
+ --light-bg: #f8f9fa;
14
+ }
15
+ body {
16
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
+ margin: 0;
18
+ background: #f5f7fa;
19
+ color: #333;
20
+ line-height: 1.6;
21
+ }
22
+ .header {
23
+ background: linear-gradient(135deg, var(--secondary), var(--primary));
24
+ color: white;
25
+ padding: 1rem 2rem;
26
+ display: flex;
27
+ justify-content: space-between;
28
+ align-items: center;
29
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
30
+ }
31
+ .back-btn {
32
+ color: white;
33
+ text-decoration: none;
34
+ font-weight: 500;
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ }
39
+ .container {
40
+ max-width: 1200px;
41
+ margin: 2rem auto;
42
+ padding: 0 1.5rem;
43
+ }
44
+ .page-title {
45
+ color: var(--secondary);
46
+ border-bottom: 2px solid var(--light-bg);
47
+ padding-bottom: 1rem;
48
+ margin-bottom: 2rem;
49
+ }
50
+ .resources-container {
51
+ display: grid;
52
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
53
+ gap: 1.5rem;
54
+ }
55
+ .resource-card {
56
+ background: white;
57
+ border-radius: 8px;
58
+ overflow: hidden;
59
+ box-shadow: 0 3px 10px rgba(0,0,0,0.08);
60
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
61
+ border-top: 4px solid var(--accent);
62
+ }
63
+ .resource-card:hover {
64
+ transform: translateY(-5px);
65
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
66
+ }
67
+ .card-header {
68
+ background: var(--light-bg);
69
+ padding: 1rem;
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 12px;
73
+ }
74
+ .card-header i {
75
+ font-size: 1.5rem;
76
+ color: var(--primary);
77
+ width: 40px;
78
+ height: 40px;
79
+ background: white;
80
+ border-radius: 50%;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
85
+ }
86
+ .card-header h3 {
87
+ margin: 0;
88
+ color: var(--secondary);
89
+ font-size: 1.1rem;
90
+ }
91
+ .card-body {
92
+ padding: 1.5rem;
93
+ }
94
+ .resource-list {
95
+ list-style: none;
96
+ padding: 0;
97
+ margin: 0;
98
+ }
99
+ .resource-item {
100
+ margin-bottom: 1rem;
101
+ padding-bottom: 1rem;
102
+ border-bottom: 1px dashed #eee;
103
+ }
104
+ .resource-item:last-child {
105
+ margin-bottom: 0;
106
+ padding-bottom: 0;
107
+ border-bottom: none;
108
+ }
109
+ .resource-link {
110
+ display: flex;
111
+ align-items: flex-start;
112
+ gap: 10px;
113
+ color: #444;
114
+ text-decoration: none;
115
+ transition: color 0.2s;
116
+ }
117
+ .resource-link:hover {
118
+ color: var(--primary);
119
+ }
120
+ .resource-link i {
121
+ color: var(--accent);
122
+ margin-top: 3px;
123
+ font-size: 0.9rem;
124
+ }
125
+ .resource-desc {
126
+ font-size: 0.85rem;
127
+ color: #666;
128
+ margin-top: 0.3rem;
129
+ margin-left: 24px;
130
+ }
131
+ .tag {
132
+ display: inline-block;
133
+ background: var(--light-bg);
134
+ color: var(--primary);
135
+ font-size: 0.7rem;
136
+ padding: 0.2rem 0.5rem;
137
+ border-radius: 4px;
138
+ margin-top: 0.5rem;
139
+ }
140
+ @media (max-width: 768px) {
141
+ .resources-container {
142
+ grid-template-columns: 1fr;
143
+ }
144
+ }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <header class="header">
149
+ <h1>Legal Resources</h1>
150
+ <a href="advocatedashboard.html" class="back-btn">
151
+ <i class="fas fa-arrow-left"></i> Back to Dashboard
152
+ </a>
153
+ </header>
154
+
155
+ <div class="container">
156
+ <h2 class="page-title">Essential Legal Tools & References</h2>
157
+
158
+ <div class="resources-container">
159
+ <!-- AI Research Tools -->
160
+
161
+
162
+ <!-- Case Law Databases -->
163
+ <div class="resource-card">
164
+ <div class="card-header">
165
+ <i class="fas fa-gavel"></i>
166
+ <h3>Case Law Databases</h3>
167
+ </div>
168
+ <div class="card-body">
169
+ <ul class="resource-list">
170
+ <li class="resource-item">
171
+ <a href="https://indiankanoon.org/" target="_blank" class="resource-link">
172
+ <i class="fas fa-external-link-alt"></i>
173
+ <div>
174
+ <strong>Indian Kanoon</strong>
175
+ <div class="resource-desc">Free Indian case law database with comprehensive search</div>
176
+ <span class="tag">Free</span>
177
+ </div>
178
+ </a>
179
+ </li>
180
+ <li class="resource-item">
181
+ <a href="https://www.scconline.com/" target="_blank" class="resource-link">
182
+ <i class="fas fa-external-link-alt"></i>
183
+ <div>
184
+ <strong>SCC Online</strong>
185
+ <div class="resource-desc">Premium case law database with editorial enhancements</div>
186
+ <span class="tag">Premium</span>
187
+ </div>
188
+ </a>
189
+ </li>
190
+ <li class="resource-item">
191
+ <a href="https://www.manupatrafast.com/" target="_blank" class="resource-link">
192
+ <i class="fas fa-external-link-alt"></i>
193
+ <div>
194
+ <strong>Manupatra</strong>
195
+ <div class="resource-desc">Indian legal research with statutes and case law</div>
196
+ <span class="tag">Subscription</span>
197
+ </div>
198
+ </a>
199
+ </li>
200
+ </ul>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- Practice Management -->
205
+ <div class="resource-card">
206
+ <div class="card-header">
207
+ <i class="fas fa-tools"></i>
208
+ <h3>Practice Management</h3>
209
+ </div>
210
+ <div class="card-body">
211
+ <ul class="resource-list">
212
+ <li class="resource-item">
213
+ <a href="https://clio.com/" target="_blank" class="resource-link">
214
+ <i class="fas fa-external-link-alt"></i>
215
+ <div>
216
+ <strong>Clio</strong>
217
+ <div class="resource-desc">Cloud-based legal practice management software</div>
218
+ <span class="tag">Cloud</span>
219
+ </div>
220
+ </a>
221
+ </li>
222
+ <li class="resource-item">
223
+ <a href="https://www.lexisnexis.com/" target="_blank" class="resource-link">
224
+ <i class="fas fa-external-link-alt"></i>
225
+ <div>
226
+ <strong>LexisNexis</strong>
227
+ <div class="resource-desc">Legal research and business analytics solutions</div>
228
+ <span class="tag">Enterprise</span>
229
+ </div>
230
+ </a>
231
+ </li>
232
+ <li class="resource-item">
233
+ <a href="https://www.lawpay.com/" target="_blank" class="resource-link">
234
+ <i class="fas fa-external-link-alt"></i>
235
+ <div>
236
+ <strong>LawPay</strong>
237
+ <div class="resource-desc">Payment processing designed for legal professionals</div>
238
+ <span class="tag">Payments</span>
239
+ </div>
240
+ </a>
241
+ </li>
242
+ </ul>
243
+ </div>
244
+ </div>
245
+
246
+ <!-- Recent Amendments -->
247
+ <div class="resource-card">
248
+ <div class="card-header">
249
+ <i class="fas fa-newspaper"></i>
250
+ <h3>2024 Legal Updates</h3>
251
+ </div>
252
+ <div class="card-body">
253
+ <ul class="resource-list">
254
+ <li class="resource-item">
255
+ <div class="resource-link">
256
+ <i class="fas fa-file-alt"></i>
257
+ <div>
258
+ <strong>BNSS (Bharatiya Nagarik Suraksha Sanhita)</strong>
259
+ <div class="resource-desc">Replaces CrPC with 533 sections, 160+ changes</div>
260
+ <span class="tag">Criminal Law</span>
261
+ </div>
262
+ </div>
263
+ </li>
264
+ <li class="resource-item">
265
+ <div class="resource-link">
266
+ <i class="fas fa-file-alt"></i>
267
+ <div>
268
+ <strong>BSS (Bharatiya Sakshya Adhiniyam)</strong>
269
+ <div class="resource-desc">New evidence law replacing Indian Evidence Act</div>
270
+ <span class="tag">Evidence</span>
271
+ </div>
272
+ </div>
273
+ </li>
274
+ <li class="resource-item">
275
+ <div class="resource-link">
276
+ <i class="fas fa-file-alt"></i>
277
+ <div>
278
+ <strong>DPDP Act 2023</strong>
279
+ <div class="resource-desc">Digital Personal Data Protection framework</div>
280
+ <span class="tag">Data Privacy</span>
281
+ </div>
282
+ </div>
283
+ </li>
284
+ </ul>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+
290
+ <script>
291
+ // Simple token verification
292
+ document.getElementById("chatbotButton").addEventListener("click", function() {
293
+ window.location.href = "advocatechatbot.html";
294
+ });
295
+ </script>
296
+ </body>
297
+ </html>
src/apps/templates/citizen.html ADDED
@@ -0,0 +1,791 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Law bot</title>
8
+ <link rel="stylesheet" href="/static/css/entrance.css">
9
+ <script src="/static/js/entrance.js" defer></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="/static/css/modern-chat.css">
12
+ <style>
13
+ :root {
14
+ --primary-purple: #9b87f5;
15
+ --dark-purple: #1A1F2C;
16
+ --light-purple: #D6BCFA;
17
+ --neutral-gray: #8E9196;
18
+ --light-gray: #C8C8C9;
19
+ --off-white: #eee;
20
+ }
21
+
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ font-family: 'Inter', sans-serif;
27
+ }
28
+
29
+ body {
30
+ transition: background-color 0.3s, color 0.3s;
31
+ }
32
+
33
+ body.dark {
34
+ color: var(--off-white);
35
+ }
36
+
37
+ body.light {
38
+ color: var(--dark-purple);
39
+ }
40
+
41
+ .container {
42
+ max-width: 800px;
43
+ margin: 0 auto;
44
+ padding: 2rem;
45
+ min-height: 100vh;
46
+ display: flex;
47
+ flex-direction: column;
48
+ }
49
+
50
+ .header {
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ margin-bottom: 2rem;
55
+ }
56
+
57
+ .app-title {
58
+ margin-left: 40px;
59
+ font-size: 2rem;
60
+ font-weight: 600;
61
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
62
+ -webkit-background-clip: text;
63
+ background-clip: text;
64
+ color: transparent;
65
+ }
66
+
67
+ .theme-toggle {
68
+ background: none;
69
+ border: none;
70
+ cursor: pointer;
71
+ padding: 0.5rem;
72
+ color: var(--primary-purple);
73
+ transition: opacity 0.3s;
74
+ }
75
+
76
+ .theme-toggle:hover {
77
+ opacity: 0.8;
78
+ }
79
+
80
+ .theme-toggle svg {
81
+ width: 24px;
82
+ height: 24px;
83
+ }
84
+
85
+ .input-wrapper {
86
+ max-width: 800px;
87
+ margin: 0 auto;
88
+ position: relative;
89
+ }
90
+
91
+ #messageInput {
92
+ width: 100%;
93
+ padding: 1rem;
94
+ padding-right: 3rem;
95
+ border: 1px solid rgba(200, 200, 201, 0.3);
96
+ border-radius: 8px;
97
+ font-size: 1rem;
98
+ outline: none;
99
+ transition: border-color 0.3s;
100
+ background-color: transparent;
101
+ color: inherit;
102
+ }
103
+
104
+ body.light #messageInput {
105
+ border: 1px solid var(--light-gray);
106
+ }
107
+
108
+ #messageInput:focus {
109
+ border-color: var(--primary-purple);
110
+ }
111
+
112
+ #sendButton {
113
+ position: absolute;
114
+ right: 0.5rem;
115
+ top: 50%;
116
+ transform: translateY(-50%);
117
+ background: none;
118
+ border: none;
119
+ cursor: pointer;
120
+ padding: 0.5rem;
121
+ color: var(--primary-purple);
122
+ opacity: 0.8;
123
+ transition: opacity 0.3s;
124
+ }
125
+
126
+ #sendButton:hover {
127
+ opacity: 1;
128
+ }
129
+
130
+ .typing-indicator {
131
+ padding: 1rem;
132
+ color: var(--neutral-gray);
133
+ font-style: italic;
134
+ display: none;
135
+ }
136
+
137
+ .welcome-title {
138
+ margin: 6rem 0 0.5rem 0;
139
+ text-align: center;
140
+ color: var(--neutral-gray);
141
+ padding: 1rem;
142
+ font-size: 1.24rem;
143
+ animation: fadeIn 0.3s ease-in;
144
+ }
145
+
146
+ .welcome-subtitle {
147
+ line-height: 1.6;
148
+ font-size: 1.1rem;
149
+ color: var(--neutral-gray);
150
+ max-width: 800px;
151
+ margin: 0 auto;
152
+ font-weight: 400;
153
+ color: var(--primary-purple);
154
+ }
155
+
156
+ .sidebar-welcome {
157
+ margin-bottom: 2rem;
158
+ padding: 0 1rem;
159
+ border-bottom: none;
160
+ }
161
+
162
+ .role-selection-link {
163
+ display: inline-flex;
164
+ align-items: center;
165
+ gap: initial;
166
+ color: var(--light-purple);
167
+ text-decoration: none;
168
+ margin-top: auto;
169
+ padding: auto;
170
+ transition: opacity 0.3s ease;
171
+ position: absolute;
172
+ top: 550px;
173
+ left: 30px;
174
+ }
175
+
176
+ .role-selection-link:hover {
177
+ opacity: 0.8;
178
+ }
179
+
180
+ .role-selection-link svg {
181
+ width: 16px;
182
+ height: 16px;
183
+ stroke: currentColor;
184
+ }
185
+
186
+ @media (max-width: 480px) {
187
+ .welcome-message p {
188
+ font-size: 1rem;
189
+ padding: 0 1rem;
190
+ }
191
+ }
192
+
193
+ @keyframes fadeIn {
194
+ from {
195
+ opacity: 0;
196
+ transform: translateY(10px);
197
+ }
198
+
199
+ to {
200
+ opacity: 1;
201
+ transform: translateY(0);
202
+ }
203
+ }
204
+
205
+ .connection-status {
206
+ position: fixed;
207
+ top: 1rem;
208
+ right: 1rem;
209
+ padding: 0.5rem 1rem;
210
+ border-radius: 4px;
211
+ font-size: 0.875rem;
212
+ display: none;
213
+ }
214
+
215
+ .connection-status.connected {
216
+ background-color: #4ade80;
217
+ color: white;
218
+ }
219
+
220
+ .connection-status.disconnected {
221
+ background-color: #ef4444;
222
+ color: white;
223
+ }
224
+
225
+ .message-content {
226
+ line-height: 1.5;
227
+ }
228
+
229
+ .message-content h1,
230
+ .message-content h2,
231
+ .message-content h3 {
232
+ margin: 1rem 0 0.5rem;
233
+ font-weight: 600;
234
+ }
235
+
236
+ .message-content h1 {
237
+ font-size: 1.5rem;
238
+ }
239
+
240
+ .message-content h2 {
241
+ font-size: 1.25rem;
242
+ }
243
+
244
+ .message-content h3 {
245
+ font-size: 1.1rem;
246
+ }
247
+
248
+ .message-content ul,
249
+ .message-content ol {
250
+ margin-left: 1.5rem;
251
+ margin-bottom: 1rem;
252
+ }
253
+
254
+ .message-content a {
255
+ color: var(--primary-purple);
256
+ text-decoration: none;
257
+ }
258
+
259
+ .message-content a:hover {
260
+ text-decoration: underline;
261
+ }
262
+
263
+ .usage-indicator {
264
+ padding: 4px 12px;
265
+ background: rgba(155, 135, 245, 0.1);
266
+ border: 1px solid rgba(155, 135, 245, 0.3);
267
+ border-radius: 20px;
268
+ font-size: 0.8rem;
269
+ color: var(--primary-purple);
270
+ font-weight: 500;
271
+ display: none;
272
+ /* Hidden by default for Admins */
273
+ }
274
+ </style>
275
+ </head>
276
+
277
+ <body>
278
+
279
+
280
+
281
+ <button class="sidebar-floating-toggle" id="floatingToggle">
282
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
283
+ <path d="M3 12h18M3 6h18M3 18h18" />
284
+ </svg>
285
+ </button>
286
+
287
+ <aside class="sidebar" id="sidebar">
288
+ <div class="sidebar-header">
289
+ <button class="new-chat-btn" id="newChatBtn">
290
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
291
+ <path d="M12 5v14M5 12h14" />
292
+ </svg>
293
+ New Chat
294
+ </button>
295
+ <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
296
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
297
+ <path d="M15 18l-6-6 6-6" />
298
+ </svg>
299
+ </button>
300
+ </div>
301
+ <div class="sidebar-title">Recent Chats</div>
302
+ <div class="history-list" id="historyList">
303
+ <!-- Recent prompts will appear here -->
304
+ </div>
305
+
306
+ <!-- HIDDEN Answer Perspective for Citizen Role (Strict Isolation) -->
307
+ <div class="perspective-container" style="display: none;">
308
+ <div class="sidebar-title">Answer Perspective</div>
309
+ <div class="perspective-list">
310
+ <div class="perspective-option active" data-role="Citizen">👨‍⚖️ Citizen</div>
311
+ </div>
312
+ </div>
313
+
314
+
315
+ </aside>
316
+
317
+ <!-- Existing messages will be added here dynamically -->
318
+ <div class="container">
319
+ <header class="header">
320
+ <h1 class="app-title">Law Bot (Citizen)</h1>
321
+ <div class="header-right">
322
+ <div class="usage-indicator" id="usageIndicator">
323
+ Questions Remaining: <span id="remainingCount">--</span>
324
+ </div>
325
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
326
+ <!-- Icon injected by JS -->
327
+ </button>
328
+ <div class="user-profile-menu" id="userProfileMenu">
329
+ <div class="profile-icon-btn">
330
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
331
+ stroke-width="2">
332
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
333
+ <circle cx="12" cy="7" r="4"></circle>
334
+ </svg>
335
+ </div>
336
+ <div class="dropdown-menu" id="dropdownMenu">
337
+ <div class="dropdown-item role-info">
338
+ Role: Citizen
339
+ </div>
340
+ <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
341
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
342
+ stroke-width="2">
343
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
344
+ <polyline points="16 17 21 12 16 7"></polyline>
345
+ <line x1="21" y1="12" x2="9" y2="12"></line>
346
+ </svg>
347
+ Logout
348
+ </a>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </header>
353
+ <div class="welcome-title">Welcome, Responsible Citizen! 👨‍⚖️</div>
354
+ <div class="welcome-subtitle">Let’s simplify legal processes together. How can I help clarify the laws for
355
+ you?
356
+ </div>
357
+
358
+
359
+ <div class="chat-container" id="chatContainer">
360
+ <div class="typing-indicator" id="typingIndicator">
361
+ <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--primary-purple)" stroke-width="2">
362
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
363
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
364
+ </svg>
365
+ <span>Law Bot is thinking...</span>
366
+ </div>
367
+ </div>
368
+ <div class="input-container">
369
+ <div class="input-wrapper">
370
+ <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
371
+ <button id="sendButton">
372
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
373
+ stroke-linecap="round" stroke-linejoin="round">
374
+ <line x1="22" y1="2" x2="11" y2="13"></line>
375
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
376
+ </svg>
377
+ </button>
378
+ </div>
379
+ </div>
380
+ <footer class="legal-footer">
381
+ &copy; 2026 Law Bot AI. Professional Legal Assistant for Citizens.
382
+ <br>Disclaimer: This AI system provides information for educational/assisting purposes and does not
383
+ constitute formal legal advice.
384
+ </footer>
385
+ </div>
386
+ <div class="connection-status" id="connectionStatus"></div>
387
+
388
+ <script>
389
+ let ws;
390
+ const chatContainer = document.getElementById('chatContainer');
391
+ const messageInput = document.getElementById('messageInput');
392
+ const sendButton = document.getElementById('sendButton');
393
+ const typingIndicator = document.getElementById('typingIndicator');
394
+ const connectionStatus = document.getElementById('connectionStatus');
395
+ const sidebar = document.getElementById('sidebar');
396
+
397
+ let currentUserMessage = '';
398
+ let currentAiMessage = '';
399
+ let currentAiMessageElement = null;
400
+ let currentCaseId = crypto.randomUUID();
401
+ const activeRole = 'Citizen'; // LOCKED ROLE
402
+ let userLimitReached = false;
403
+
404
+ async function checkUserStatus() {
405
+ const token = localStorage.getItem('token');
406
+ if (!token) return;
407
+
408
+ try {
409
+ const response = await fetch('/api/user-status', {
410
+ headers: { 'Authorization': `Bearer ${token}` }
411
+ });
412
+ const status = await response.json();
413
+
414
+ const indicator = document.getElementById('usageIndicator');
415
+ if (status.is_admin) {
416
+ indicator.style.display = 'none';
417
+ } else {
418
+ indicator.style.display = 'block';
419
+ const remaining = Math.max(0, 2 - status.question_count);
420
+ document.getElementById('remainingCount').innerText = remaining;
421
+ if (remaining <= 0) {
422
+ userLimitReached = true;
423
+ }
424
+ }
425
+ } catch (err) {
426
+ console.error('Failed to fetch user status:', err);
427
+ }
428
+ }
429
+
430
+ function formatText(text) {
431
+ // Extract references for Evidence Box
432
+ const references = [];
433
+ const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
434
+ let match;
435
+ while ((match = refRegex.exec(text)) !== null) {
436
+ references.push({ title: match[1], pages: match[2] });
437
+ }
438
+
439
+ let formatted = text
440
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
441
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
442
+ .replace(/### (.*?)\n/g, "<h3>$1</h3>")
443
+ .replace(/## (.*?)\n/g, "<h2>$1</h2>")
444
+ .replace(/# (.*?)\n/g, "<h1>$1</h1>")
445
+ .replace(/\n/g, "<br>")
446
+ .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
447
+ .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
448
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
449
+
450
+ // Add Evidence Box if references found
451
+ if (references.length > 0) {
452
+ let evidenceHtml = `<div class="evidence-box">
453
+ <div class="evidence-title">
454
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
455
+ Study Evidence
456
+ </div>`;
457
+ references.forEach(ref => {
458
+ evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
459
+ });
460
+ evidenceHtml += `</div>`;
461
+ formatted += evidenceHtml;
462
+ }
463
+ return formatted;
464
+ }
465
+
466
+ function connectWebSocket() {
467
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
468
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
469
+
470
+ ws.onopen = () => {
471
+ console.log('Connected to WebSocket');
472
+ connectionStatus.textContent = 'Connected';
473
+ connectionStatus.className = 'connection-status connected';
474
+ connectionStatus.style.display = 'block';
475
+ setTimeout(() => {
476
+ connectionStatus.style.display = 'none';
477
+ }, 3000);
478
+ };
479
+
480
+ ws.onclose = () => {
481
+ // ...
482
+ setTimeout(() => {
483
+ reconnectWebSocket();
484
+ }, 5000);
485
+ };
486
+
487
+ ws.onmessage = (event) => {
488
+ console.log('Received message:', event.data);
489
+
490
+ // ✅ Hide typing indicator on response
491
+ typingIndicator.style.display = 'none';
492
+
493
+ if (event.data === '[DONE]') {
494
+ // Save complete chat interaction with Role
495
+ saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
496
+ checkUserStatus(); // Update remaining count
497
+ return;
498
+ }
499
+
500
+ // Handle usage limit blocks from backend
501
+ if (event.data.includes("Free usage limit reached")) {
502
+ typingIndicator.style.display = 'none';
503
+ userLimitReached = true;
504
+ checkUserStatus();
505
+ }
506
+
507
+ if (!currentAiMessageElement) {
508
+ currentAiMessage = event.data;
509
+ addMessage(currentAiMessage, 'ai');
510
+ } else {
511
+ currentAiMessage += event.data;
512
+ const formattedMessage = formatText(currentAiMessage);
513
+ currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
514
+ }
515
+ };
516
+
517
+ // ... (existing scroll logic) ...
518
+ chatContainer.scrollTop = chatContainer.scrollHeight;
519
+ }
520
+
521
+ function reconnectWebSocket() {
522
+ console.log('Attempting to reconnect...');
523
+ connectWebSocket();
524
+ loadHistory();
525
+ }
526
+
527
+ function addMessage(content, type) {
528
+ if (type === 'user') {
529
+ currentUserMessage = content;
530
+ currentAiMessage = '';
531
+ currentAiMessageElement = null;
532
+ }
533
+
534
+ const messageDiv = document.createElement('div');
535
+ messageDiv.className = `message ${type}-message`;
536
+
537
+ const header = document.createElement('div');
538
+ header.className = 'message-header';
539
+
540
+ const icon = document.createElement('div');
541
+ icon.className = `${type}-icon`;
542
+ icon.textContent = type === 'user' ? 'U' : 'L';
543
+
544
+ const name = document.createElement('span');
545
+ name.textContent = type === 'user' ? 'You' : 'Law Bot';
546
+
547
+ header.appendChild(icon);
548
+ header.appendChild(name);
549
+
550
+ const text = document.createElement('div');
551
+ text.className = 'message-content';
552
+ text.innerHTML = type === 'ai' ? formatText(content) : content;
553
+
554
+ messageDiv.appendChild(header);
555
+ messageDiv.appendChild(text);
556
+ chatContainer.appendChild(messageDiv);
557
+ chatContainer.scrollTop = chatContainer.scrollHeight;
558
+
559
+ if (type === 'ai') {
560
+ currentAiMessageElement = messageDiv;
561
+ }
562
+ }
563
+
564
+ function sendMessage() {
565
+ const message = messageInput.value.trim();
566
+ if (!message) return;
567
+
568
+ if (userLimitReached) {
569
+ addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
570
+ return;
571
+ }
572
+
573
+ if (ws.readyState === WebSocket.OPEN) {
574
+ addMessage(message, 'user');
575
+ ws.send(message);
576
+ messageInput.value = '';
577
+
578
+ // ✅ Show typing indicator after sending
579
+ typingIndicator.style.display = 'block';
580
+ chatContainer.scrollTop = chatContainer.scrollHeight;
581
+ }
582
+ }
583
+ const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
584
+ if (isAtBottom) {
585
+ chatContainer.scrollTop = chatContainer.scrollHeight;
586
+ }
587
+
588
+ const historyList = document.getElementById('historyList');
589
+
590
+ async function loadHistory() {
591
+ const token = localStorage.getItem('token');
592
+ if (!token) return;
593
+
594
+ try {
595
+ // Role-locked: fetch filtered history
596
+ const response = await fetch(`/api/interactions?role=${activeRole}`, {
597
+ headers: { 'Authorization': `Bearer ${token}` }
598
+ });
599
+ const interactions = await response.json();
600
+ historyList.innerHTML = '';
601
+ interactions.forEach(item => {
602
+ const div = document.createElement('div');
603
+ div.className = 'history-item';
604
+
605
+ // Readable preview
606
+ const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
607
+
608
+ div.innerHTML = `
609
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
610
+ <span>${preview}</span>
611
+ <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
612
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
613
+ </button>
614
+ `;
615
+ div.onclick = () => loadConversation(item.case_id);
616
+ historyList.appendChild(div);
617
+ });
618
+ } catch (err) {
619
+ console.error('Failed to load history:', err);
620
+ }
621
+ }
622
+
623
+ async function loadConversation(caseId) {
624
+ const token = localStorage.getItem('token');
625
+ try {
626
+ // Role-locked conversation fetching
627
+ const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
628
+ headers: { 'Authorization': `Bearer ${token}` }
629
+ });
630
+ const thread = await response.json();
631
+
632
+ currentCaseId = caseId;
633
+ chatContainer.innerHTML = ''; // Clear window
634
+
635
+ thread.forEach(msg => {
636
+ addMessage(msg.query, 'user');
637
+ // Render AI Response immediately
638
+ const aiMsgDiv = document.createElement('div');
639
+ aiMsgDiv.className = 'message ai-message';
640
+ aiMsgDiv.innerHTML = `
641
+ <div class="message-header">
642
+ <div class="ai-icon">L</div>
643
+ <span>Law Bot</span>
644
+ </div>
645
+ <div class="message-content">${formatText(msg.response)}</div>
646
+ `;
647
+ chatContainer.appendChild(aiMsgDiv);
648
+ });
649
+ chatContainer.scrollTop = chatContainer.scrollHeight;
650
+ } catch (err) {
651
+ console.error('Failed to load thread:', err);
652
+ }
653
+ }
654
+
655
+ async function deleteConversation(caseId) {
656
+ const token = localStorage.getItem('token');
657
+ try {
658
+ await fetch(`/api/interactions/${caseId}`, {
659
+ method: 'DELETE',
660
+ headers: { 'Authorization': `Bearer ${token}` }
661
+ });
662
+ if (currentCaseId === caseId) {
663
+ newChat();
664
+ } else {
665
+ loadHistory();
666
+ }
667
+ } catch (err) {
668
+ console.error('Failed to delete:', err);
669
+ }
670
+ }
671
+
672
+ function newChat() {
673
+ currentCaseId = crypto.randomUUID();
674
+ chatContainer.innerHTML = '';
675
+ messageInput.value = '';
676
+ messageInput.focus();
677
+ loadHistory();
678
+ }
679
+
680
+ // ✅ Modified: Uses Bearer token AND Role
681
+ const saveChatInteraction = async (caseId, query, response) => {
682
+ const token = localStorage.getItem('token');
683
+ if (!token) return;
684
+
685
+ try {
686
+ await fetch('/api/save-interaction', {
687
+ method: 'POST',
688
+ headers: {
689
+ 'Content-Type': 'application/json',
690
+ 'Authorization': `Bearer ${token}`
691
+ },
692
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
693
+ });
694
+ loadHistory(); // Refresh sidebar history
695
+ } catch (error) {
696
+ console.error('Error saving chat:', error);
697
+ }
698
+ };
699
+
700
+ sendButton.addEventListener('click', sendMessage);
701
+ messageInput.addEventListener('keypress', (e) => {
702
+ if (e.key === 'Enter') {
703
+ sendMessage();
704
+ }
705
+ });
706
+
707
+ // Role Switching
708
+ document.querySelectorAll('.perspective-option').forEach(opt => {
709
+ opt.onclick = () => {
710
+ document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
711
+ opt.classList.add('active');
712
+ activeRole = opt.dataset.role;
713
+
714
+ // Refresh Context
715
+ newChat();
716
+ connectWebSocket(); // Reconnect with new role param
717
+ };
718
+ });
719
+
720
+ // --- User Profile Dropdown ---
721
+ const userProfileMenu = document.getElementById('userProfileMenu');
722
+ const dropdownMenu = document.getElementById('dropdownMenu');
723
+ const headerLogoutBtn = document.getElementById('headerLogoutBtn');
724
+
725
+ userProfileMenu.addEventListener('click', (e) => {
726
+ e.stopPropagation();
727
+ dropdownMenu.classList.toggle('show');
728
+ });
729
+
730
+ document.addEventListener('click', () => {
731
+ if (dropdownMenu.classList.contains('show')) {
732
+ dropdownMenu.classList.remove('show');
733
+ }
734
+ });
735
+
736
+ headerLogoutBtn.addEventListener('click', (e) => {
737
+ e.preventDefault();
738
+ if (window.dashboardEntrance) {
739
+ window.dashboardEntrance.logout();
740
+ } else {
741
+ localStorage.removeItem('token');
742
+ window.location.href = '/role';
743
+ }
744
+ });
745
+
746
+ // Init
747
+ const themeToggle = document.getElementById('themeToggle');
748
+ const body = document.body;
749
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
750
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
751
+
752
+ function updateTheme(isDark) {
753
+ if (isDark) {
754
+ body.classList.add('dark');
755
+ body.classList.remove('light');
756
+ themeToggle.innerHTML = moonIcon;
757
+ localStorage.setItem('theme', 'dark');
758
+ } else {
759
+ body.classList.add('light');
760
+ body.classList.remove('dark');
761
+ themeToggle.innerHTML = sunIcon;
762
+ localStorage.setItem('theme', 'light');
763
+ }
764
+ }
765
+
766
+ themeToggle.addEventListener('click', () => {
767
+ const isNowDark = !body.classList.contains('dark');
768
+ updateTheme(isNowDark);
769
+ });
770
+
771
+ // Initialize Theme
772
+ const savedTheme = localStorage.getItem('theme') || 'dark';
773
+ updateTheme(savedTheme === 'dark');
774
+
775
+ // Sidebar Collapse Logic
776
+ document.getElementById('collapseSidebar').onclick = () => {
777
+ document.body.classList.add('sidebar-collapsed');
778
+ };
779
+ document.getElementById('floatingToggle').onclick = () => {
780
+ document.body.classList.remove('sidebar-collapsed');
781
+ };
782
+ document.getElementById('newChatBtn').onclick = newChat;
783
+
784
+ checkUserStatus();
785
+ connectWebSocket();
786
+ loadHistory();
787
+
788
+ </script>
789
+ </body>
790
+
791
+ </html>
src/apps/templates/forgot_password.html ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Forgot Password - Law Bot</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
9
+ <style>
10
+ /* Reuse styles */
11
+ .auth-container {
12
+ max-width: 400px;
13
+ margin: 50px auto;
14
+ padding: 2rem;
15
+ background: rgba(255, 255, 255, 0.1);
16
+ backdrop-filter: blur(10px);
17
+ border-radius: 12px;
18
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
19
+ color: white;
20
+ }
21
+
22
+ .auth-container h2 {
23
+ text-align: center;
24
+ margin-bottom: 1.5rem;
25
+ }
26
+
27
+ .form-group {
28
+ margin-bottom: 1rem;
29
+ }
30
+
31
+ .form-group label {
32
+ display: block;
33
+ margin-bottom: 0.5rem;
34
+ }
35
+
36
+ .form-group input {
37
+ width: 100%;
38
+ padding: 0.75rem;
39
+ border: 1px solid rgba(255, 255, 255, 0.2);
40
+ border-radius: 6px;
41
+ background: rgba(0, 0, 0, 0.2);
42
+ color: white;
43
+ }
44
+
45
+ .btn {
46
+ width: 100%;
47
+ padding: 0.75rem;
48
+ background: #9b87f5;
49
+ color: white;
50
+ border: none;
51
+ border-radius: 6px;
52
+ cursor: pointer;
53
+ font-size: 1rem;
54
+ transition: background 0.3s;
55
+ }
56
+
57
+ .btn:hover {
58
+ background: #7e69d6;
59
+ }
60
+
61
+ .links {
62
+ margin-top: 1rem;
63
+ text-align: center;
64
+ font-size: 0.9rem;
65
+ }
66
+
67
+ .links a {
68
+ color: #D6BCFA;
69
+ text-decoration: none;
70
+ }
71
+
72
+ .links a:hover {
73
+ text-decoration: underline;
74
+ }
75
+ </style>
76
+ </head>
77
+
78
+ <body class="dark">
79
+ <div class="auth-container">
80
+ <h2>Forgot Password</h2>
81
+ <form id="forgotPasswordForm">
82
+ <div class="form-group">
83
+ <label for="email">Email</label>
84
+ <input type="email" id="email" required>
85
+ </div>
86
+ <button type="submit" class="btn">Send Reset Link</button>
87
+ </form>
88
+ <div class="links">
89
+ <a href="/login">Back to Login</a>
90
+ </div>
91
+ </div>
92
+
93
+ <script>
94
+ document.getElementById('forgotPasswordForm').addEventListener('submit', async (e) => {
95
+ e.preventDefault();
96
+ const email = document.getElementById('email').value;
97
+
98
+ try {
99
+ const response = await fetch('/api/forgot-password', {
100
+ method: 'POST',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({ email })
103
+ });
104
+
105
+ const data = await response.json();
106
+ if (response.ok) {
107
+ alert('If an account exists with this email, a reset link has been sent.');
108
+ } else {
109
+ alert(data.detail || 'Request failed');
110
+ }
111
+ } catch (error) {
112
+ console.error('Error:', error);
113
+ alert('An error occurred');
114
+ }
115
+ });
116
+ </script>
117
+ </body>
118
+
119
+ </html>
src/apps/templates/judgecalender.html ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Court Calendar</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background-color: #f5f7fa;
14
+ }
15
+ .header {
16
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
17
+ color: white;
18
+ padding: 1rem 2rem;
19
+ display: flex;
20
+ justify-content: space-between;
21
+ }
22
+ .calendar-container {
23
+ padding: 1rem;
24
+ }
25
+ .calendar-header {
26
+ display: flex;
27
+ justify-content: space-between;
28
+ align-items: center;
29
+ margin-bottom: 1rem;
30
+ }
31
+ .calendar-nav button {
32
+ background: #1e3c72;
33
+ color: white;
34
+ border: none;
35
+ padding: 5px 10px;
36
+ border-radius: 5px;
37
+ cursor: pointer;
38
+ }
39
+ .calendar-grid {
40
+ display: grid;
41
+ grid-template-columns: repeat(7, 1fr);
42
+ gap: 5px;
43
+ }
44
+ .calendar-day-header {
45
+ text-align: center;
46
+ font-weight: bold;
47
+ padding: 10px;
48
+ background: #e6f0ff;
49
+ }
50
+ .calendar-day {
51
+ border: 1px solid #ddd;
52
+ min-height: 100px;
53
+ padding: 5px;
54
+ }
55
+ .event {
56
+ background: #e6f0ff;
57
+ padding: 2px 5px;
58
+ margin: 2px 0;
59
+ border-radius: 3px;
60
+ font-size: 0.8rem;
61
+ }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ <header class="header">
66
+ <h1>Court Calendar</h1>
67
+ <a href="judgedashboard.html" style="color: white;">← Back to Dashboard</a>
68
+ </header>
69
+
70
+ <div class="calendar-container">
71
+ <div class="calendar-header">
72
+ <h2 id="month-year">April 2024</h2>
73
+ <div class="calendar-nav">
74
+ <button id="prev-month"><i class="fas fa-chevron-left"></i></button>
75
+ <button id="next-month"><i class="fas fa-chevron-right"></i></button>
76
+ </div>
77
+ </div>
78
+
79
+ <div class="calendar-grid" id="day-headers">
80
+ <div class="calendar-day-header">Sun</div>
81
+ <div class="calendar-day-header">Mon</div>
82
+ <div class="calendar-day-header">Tue</div>
83
+ <div class="calendar-day-header">Wed</div>
84
+ <div class="calendar-day-header">Thu</div>
85
+ <div class="calendar-day-header">Fri</div>
86
+ <div class="calendar-day-header">Sat</div>
87
+ </div>
88
+
89
+ <div class="calendar-grid" id="calendar-days">
90
+ <!-- Days will be filled by JavaScript -->
91
+ </div>
92
+ </div>
93
+
94
+ <script>
95
+ // Calendar functionality
96
+ let currentDate = new Date();
97
+
98
+ function renderCalendar() {
99
+ const monthYear = document.getElementById('month-year');
100
+ const calendarDays = document.getElementById('calendar-days');
101
+
102
+ // Set month/year header
103
+ monthYear.textContent = new Intl.DateTimeFormat('en-US', {
104
+ month: 'long',
105
+ year: 'numeric'
106
+ }).format(currentDate);
107
+
108
+ // Clear previous days
109
+ calendarDays.innerHTML = '';
110
+
111
+ // Get first day of month and total days
112
+ const firstDay = new Date(
113
+ currentDate.getFullYear(),
114
+ currentDate.getMonth(),
115
+ 1
116
+ ).getDay();
117
+
118
+ const daysInMonth = new Date(
119
+ currentDate.getFullYear(),
120
+ currentDate.getMonth() + 1,
121
+ 0
122
+ ).getDate();
123
+
124
+ // Add empty cells for days before first day
125
+ for(let i = 0; i < firstDay; i++) {
126
+ const emptyDay = document.createElement('div');
127
+ emptyDay.className = 'calendar-day';
128
+ calendarDays.appendChild(emptyDay);
129
+ }
130
+
131
+ // Add days of the month
132
+ for(let day = 1; day <= daysInMonth; day++) {
133
+ const dayElement = document.createElement('div');
134
+ dayElement.className = 'calendar-day';
135
+ dayElement.textContent = day;
136
+
137
+ // Add sample events (replace with real data)
138
+ if(day === 5) {
139
+ const event = document.createElement('div');
140
+ event.className = 'event';
141
+ event.textContent = '10:00 AM - State vs. Johnson';
142
+ dayElement.appendChild(event);
143
+ }
144
+
145
+ calendarDays.appendChild(dayElement);
146
+ }
147
+ }
148
+
149
+ // Navigation
150
+ document.getElementById('prev-month').addEventListener('click', () => {
151
+ currentDate.setMonth(currentDate.getMonth() - 1);
152
+ renderCalendar();
153
+ });
154
+
155
+ document.getElementById('next-month').addEventListener('click', () => {
156
+ currentDate.setMonth(currentDate.getMonth() + 1);
157
+ renderCalendar();
158
+ });
159
+
160
+ // Initial render
161
+ renderCalendar();
162
+ </script>
163
+ </body>
164
+ </html>
src/apps/templates/judgedashboard.html ADDED
@@ -0,0 +1,697 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Judge Dashboard</title>
8
+ <link rel="stylesheet" href="/static/css/entrance.css">
9
+ <script src="/static/js/entrance.js" defer></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ /* Reset and base styles */
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
+ }
19
+
20
+ body {
21
+ background-color: #f5f7fa;
22
+ color: #333;
23
+ line-height: 1.6;
24
+ }
25
+
26
+ /* Header styles */
27
+ .header {
28
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
29
+ color: white;
30
+ padding: 1rem 2rem;
31
+ display: flex;
32
+ justify-content: space-between;
33
+ align-items: center;
34
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
35
+ position: sticky;
36
+ top: 0;
37
+ z-index: 100;
38
+ }
39
+
40
+ .header h1 {
41
+ font-size: 1.8rem;
42
+ font-weight: 600;
43
+ }
44
+
45
+ .user-info {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 1rem;
49
+ }
50
+
51
+ .user-info img {
52
+ width: 40px;
53
+ height: 40px;
54
+ border-radius: 50%;
55
+ object-fit: cover;
56
+ border: 2px solid rgba(255, 255, 255, 0.3);
57
+ }
58
+
59
+ /* Main container */
60
+ .container {
61
+ display: grid;
62
+ grid-template-columns: 250px 1fr;
63
+ min-height: calc(100vh - 70px);
64
+ }
65
+
66
+ /* Sidebar styles */
67
+ .sidebar {
68
+ background: white;
69
+ padding: 1.5rem 1rem;
70
+ box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
71
+ height: 100%;
72
+ }
73
+
74
+ .sidebar-menu {
75
+ list-style: none;
76
+ margin-top: 2rem;
77
+ }
78
+
79
+ .sidebar-menu li {
80
+ margin-bottom: 1rem;
81
+ }
82
+
83
+ .sidebar-menu a {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 0.8rem;
87
+ padding: 0.8rem 1rem;
88
+ border-radius: 8px;
89
+ color: #555;
90
+ text-decoration: none;
91
+ transition: all 0.3s ease;
92
+
93
+ }
94
+
95
+ .sidebar-menu a:hover,
96
+ .sidebar-menu a.active {
97
+ background-color: #e6f0ff;
98
+ color: #1e3c72;
99
+ }
100
+
101
+ .sidebar-menu i {
102
+ font-size: 1.2rem;
103
+ }
104
+
105
+ /* Main content */
106
+ .main-content {
107
+ padding: 2rem;
108
+ }
109
+
110
+ .dashboard-header {
111
+ display: flex;
112
+ justify-content: space-between;
113
+ align-items: center;
114
+ margin-bottom: 2rem;
115
+ }
116
+
117
+ .dashboard-header h2 {
118
+ font-size: 1.5rem;
119
+ color: #1e3c72;
120
+ }
121
+
122
+ .stats-container {
123
+ display: grid;
124
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
125
+ gap: 1.5rem;
126
+ margin-bottom: 2rem;
127
+ }
128
+
129
+ .stat-card {
130
+ background: white;
131
+ padding: 1.5rem;
132
+ border-radius: 10px;
133
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
134
+ transition: transform 0.3s ease;
135
+ }
136
+
137
+ .stat-card:hover {
138
+ transform: translateY(-5px);
139
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
140
+ }
141
+
142
+ .stat-card h3 {
143
+ font-size: 0.9rem;
144
+ color: #666;
145
+ margin-bottom: 0.5rem;
146
+ }
147
+
148
+ .stat-card p {
149
+ font-size: 1.8rem;
150
+ font-weight: 600;
151
+ color: #1e3c72;
152
+ }
153
+
154
+ /* Cases section */
155
+ .cases-section {
156
+ background: white;
157
+ border-radius: 10px;
158
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
159
+ padding: 1.5rem;
160
+ margin-bottom: 2rem;
161
+ }
162
+
163
+ .section-header {
164
+ display: flex;
165
+ justify-content: space-between;
166
+ align-items: center;
167
+ margin-bottom: 1.5rem;
168
+ }
169
+
170
+ .section-header h3 {
171
+ font-size: 1.3rem;
172
+ color: #1e3c72;
173
+ }
174
+
175
+ .view-all {
176
+ color: #1e3c72;
177
+ text-decoration: none;
178
+ font-weight: 500;
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 0.5rem;
182
+ }
183
+
184
+ .cases-grid {
185
+ display: grid;
186
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
187
+ gap: 1.5rem;
188
+ }
189
+
190
+ .case-card {
191
+ border: 1px solid #e0e0e0;
192
+ border-radius: 8px;
193
+ padding: 1.2rem;
194
+ transition: all 0.3s ease;
195
+ }
196
+
197
+ .case-card:hover {
198
+ border-color: #1e3c72;
199
+ box-shadow: 0 4px 12px rgba(30, 60, 114, 0.1);
200
+ }
201
+
202
+ .case-card h4 {
203
+ font-size: 1.1rem;
204
+ margin-bottom: 0.8rem;
205
+ color: #333;
206
+ }
207
+
208
+ .case-meta {
209
+ display: flex;
210
+ gap: 1rem;
211
+ margin-bottom: 1rem;
212
+ font-size: 0.85rem;
213
+ color: #666;
214
+ }
215
+
216
+ .case-meta span {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 0.3rem;
220
+ }
221
+
222
+ .case-meta i {
223
+ font-size: 0.9rem;
224
+ }
225
+
226
+ .case-status {
227
+ display: inline-block;
228
+ padding: 0.3rem 0.8rem;
229
+ border-radius: 20px;
230
+ font-size: 0.8rem;
231
+ font-weight: 500;
232
+ margin-bottom: 1rem;
233
+ }
234
+
235
+ .status-pending {
236
+ background-color: #fff3cd;
237
+ color: #856404;
238
+ }
239
+
240
+ .status-in-progress {
241
+ background-color: #cce5ff;
242
+ color: #004085;
243
+ }
244
+
245
+ .status-completed {
246
+ background-color: #d4edda;
247
+ color: #155724;
248
+ }
249
+
250
+ .case-actions {
251
+ display: flex;
252
+ gap: 0.8rem;
253
+ margin-top: 1rem;
254
+ }
255
+
256
+ .btn {
257
+ padding: 0.5rem 1rem;
258
+ border-radius: 5px;
259
+ border: none;
260
+ cursor: pointer;
261
+ font-weight: 500;
262
+ transition: all 0.3s ease;
263
+ display: inline-flex;
264
+ align-items: center;
265
+ gap: 0.5rem;
266
+ }
267
+
268
+ .btn-primary {
269
+ background-color: #1e3c72;
270
+ color: white;
271
+ }
272
+
273
+ .btn-primary:hover {
274
+ background-color: #2a5298;
275
+ }
276
+
277
+ .btn-outline {
278
+ background: transparent;
279
+ border: 1px solid #1e3c72;
280
+ color: #1e3c72;
281
+ }
282
+
283
+ .btn-outline:hover {
284
+ background-color: #f0f5ff;
285
+ }
286
+
287
+ /* Chatbot button */
288
+ .chatbot-btn {
289
+ position: fixed;
290
+ bottom: 2rem;
291
+ right: 2rem;
292
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
293
+ color: white;
294
+ width: 60px;
295
+ height: 60px;
296
+ border-radius: 50%;
297
+ display: flex;
298
+ justify-content: center;
299
+ align-items: center;
300
+ box-shadow: 0 4px 20px rgba(30, 60, 114, 0.3);
301
+ cursor: pointer;
302
+ transition: all 0.3s ease;
303
+ z-index: 90;
304
+ }
305
+
306
+ .chatbot-btn:hover {
307
+ transform: scale(1.1);
308
+ box-shadow: 0 6px 25px rgba(30, 60, 114, 0.4);
309
+ }
310
+
311
+ .chatbot-btn i {
312
+ font-size: 1.5rem;
313
+ }
314
+
315
+ /* Responsive adjustments */
316
+ @media (max-width: 992px) {
317
+ .container {
318
+ grid-template-columns: 1fr;
319
+ }
320
+
321
+ .sidebar {
322
+ display: none;
323
+ }
324
+
325
+ .stats-container {
326
+ grid-template-columns: repeat(2, 1fr);
327
+ }
328
+ }
329
+
330
+ @media (max-width: 768px) {
331
+ .stats-container {
332
+ grid-template-columns: 1fr;
333
+ }
334
+
335
+ .cases-grid {
336
+ grid-template-columns: 1fr;
337
+ }
338
+
339
+ .header h1 {
340
+ font-size: 1.4rem;
341
+ }
342
+ }
343
+
344
+ /* Animation for case cards */
345
+ @keyframes fadeInUp {
346
+ from {
347
+ opacity: 0;
348
+ transform: translateY(20px);
349
+ }
350
+
351
+ to {
352
+ opacity: 1;
353
+ transform: translateY(0);
354
+ }
355
+ }
356
+
357
+ .case-card {
358
+ animation: fadeInUp 0.5s ease-out forwards;
359
+ opacity: 0;
360
+ }
361
+
362
+ .case-card:nth-child(1) {
363
+ animation-delay: 0.1s;
364
+ }
365
+
366
+ .case-card:nth-child(2) {
367
+ animation-delay: 0.2s;
368
+ }
369
+
370
+ .case-card:nth-child(3) {
371
+ animation-delay: 0.3s;
372
+ }
373
+
374
+ .case-card:nth-child(4) {
375
+ animation-delay: 0.4s;
376
+ }
377
+
378
+ .case-card:nth-child(4) {
379
+ animation-delay: 0.4s;
380
+ }
381
+
382
+ /* --- USER DROPDOWN --- */
383
+ .user-profile-menu {
384
+ position: relative;
385
+ cursor: pointer;
386
+ }
387
+
388
+ .user-info {
389
+ cursor: pointer;
390
+ transition: opacity 0.3s;
391
+ }
392
+
393
+ .user-info:hover {
394
+ opacity: 0.9;
395
+ }
396
+
397
+ .dropdown-menu {
398
+ position: absolute;
399
+ top: 150%;
400
+ right: 0;
401
+ width: 200px;
402
+ background: white;
403
+ /* Adapted for Judge Light theme */
404
+ border-radius: 8px;
405
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
406
+ display: none;
407
+ flex-direction: column;
408
+ overflow: hidden;
409
+ z-index: 1000;
410
+ }
411
+
412
+ .dropdown-menu.show {
413
+ display: flex;
414
+ animation: fadeIn 0.2s ease-out;
415
+ }
416
+
417
+ .dropdown-item {
418
+ padding: 1rem;
419
+ color: #333;
420
+ text-decoration: none;
421
+ display: flex;
422
+ align-items: center;
423
+ gap: 0.5rem;
424
+ transition: background 0.2s;
425
+ border-bottom: 1px solid #f0f0f0;
426
+ }
427
+
428
+ .dropdown-item:last-child {
429
+ border-bottom: none;
430
+ }
431
+
432
+ .dropdown-item:hover {
433
+ background: #f8f9fa;
434
+ color: #1e3c72;
435
+ }
436
+
437
+ .dropdown-item.role-info {
438
+ background: #eef2f7;
439
+ font-weight: 600;
440
+ color: #1e3c72;
441
+ cursor: default;
442
+ }
443
+
444
+ .logout-action {
445
+ color: #dc3545;
446
+ }
447
+
448
+ .logout-action:hover {
449
+ background: #fff5f5;
450
+ }
451
+
452
+ .dropdown-item.usage-info {
453
+ background: #fff5f5;
454
+ font-size: 0.85rem;
455
+ color: #e53e3e;
456
+ font-weight: 600;
457
+ display: none;
458
+ padding: 1rem;
459
+ border-bottom: 1px solid #f0f0f0;
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 0.5rem;
463
+ }
464
+ </style>
465
+ </head>
466
+
467
+ <body>
468
+ <!-- Header -->
469
+ <header class="header">
470
+ <h1>Judge Dashboard</h1>
471
+ <div class="user-profile-menu" id="userProfileMenu">
472
+ <div class="user-info">
473
+ <span>Honorable Judge Smith</span>
474
+ <img src="/static/images/judge.jpg" alt="Judge Profile Picture">
475
+ <i class="fas fa-chevron-down" style="font-size: 0.8rem; margin-left: 0.5rem; opacity: 0.8;"></i>
476
+ </div>
477
+ <div class="dropdown-menu" id="dropdownMenu">
478
+ <div class="dropdown-item role-info">
479
+ <i class="fas fa-user-shield"></i> Role: Judge
480
+ </div>
481
+ <div class="dropdown-item usage-info" id="usageInfo">
482
+ <i class="fas fa-info-circle"></i> Questions Remaining: <span id="remainingCount">--</span>
483
+ </div>
484
+ <div class="dropdown-item logout-action" id="headerLogoutBtn">
485
+ <i class="fas fa-sign-out-alt"></i> Logout
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </header>
490
+
491
+ <!-- Main Container -->
492
+ <div class="container">
493
+ <!-- Sidebar -->
494
+ <aside class="sidebar">
495
+
496
+ </aside>
497
+
498
+ <!-- Main Content -->
499
+ <main class="main-content">
500
+ <div class="dashboard-header">
501
+ <h2>Today's Overview</h2>
502
+ <div class="date-display">Friday, April 5, 2024</div>
503
+ </div>
504
+
505
+ <!-- Stats Cards -->
506
+ <div class="stats-container">
507
+ <div class="stat-card">
508
+ <h3>Pending Cases</h3>
509
+ <p>24</p>
510
+ </div>
511
+ <div class="stat-card">
512
+ <h3>Today's Hearings</h3>
513
+ <p>5</p>
514
+ </div>
515
+ <div class="stat-card">
516
+ <h3>Judgments Due</h3>
517
+ <p>3</p>
518
+ </div>
519
+ <div class="stat-card">
520
+ <h3>Urgent Matters</h3>
521
+ <p>2</p>
522
+ </div>
523
+ </div>
524
+
525
+ <!-- Recent Cases Section -->
526
+ <section class="cases-section">
527
+ <div class="section-header">
528
+ <h3>Recent Cases</h3>
529
+ <a href="viewall.html" class="view-all">View All <i class="fas fa-chevron-right"></i></a>
530
+ </div>
531
+ <div class="cases-grid">
532
+ <!-- Case Card 1 -->
533
+ <div class="case-card">
534
+ <h4>State vs. Johnson</h4>
535
+ <div class="case-meta">
536
+ <span><i class="fas fa-hashtag"></i> CR-2024-0456</span>
537
+ <span><i class="fas fa-clock"></i> 10:30 AM</span>
538
+ </div>
539
+ <span class="case-status status-pending">Pending Judgment</span>
540
+ <p>Criminal case involving charges of fraud and embezzlement. Defense has filed motion to
541
+ suppress.</p>
542
+
543
+ </div>
544
+
545
+ <!-- Case Card 2 -->
546
+ <div class="case-card">
547
+ <h4>Doe vs. Smith Corporation</h4>
548
+ <div class="case-meta">
549
+ <span><i class="fas fa-hashtag"></i> CV-2024-0789</span>
550
+ <span><i class="fas fa-clock"></i> 02:15 PM</span>
551
+ </div>
552
+ <span class="case-status status-in-progress">In Progress</span>
553
+ <p>Civil lawsuit regarding wrongful termination. Plaintiff seeking damages of $2.5 million.</p>
554
+
555
+ </div>
556
+
557
+ <!-- Case Card 3 -->
558
+ <div class="case-card">
559
+ <h4>In re: Peterson Estate</h4>
560
+ <div class="case-meta">
561
+ <span><i class="fas fa-hashtag"></i> PR-2024-0123</span>
562
+ <span><i class="fas fa-clock"></i> 11:00 AM</span>
563
+ </div>
564
+ <span class="case-status status-completed">Completed</span>
565
+ <p>Probate matter with dispute among heirs regarding distribution of assets.</p>
566
+
567
+ </div>
568
+
569
+
570
+ </div>
571
+ </section>
572
+
573
+ <!-- Upcoming Hearings Section -->
574
+ <section class="cases-section">
575
+ <div class="section-header">
576
+ <h3>Upcoming Hearings</h3>
577
+ <a href="judgecalender.html" class="view-all">View Calendar <i class="fas fa-chevron-right"></i></a>
578
+ </div>
579
+ <div class="cases-grid">
580
+ <!-- Hearing Card 1 -->
581
+ <div class="case-card">
582
+ <h4>Bail Hearing: State vs. Rodriguez</h4>
583
+ <div class="case-meta">
584
+ <span><i class="fas fa-hashtag"></i> CR-2024-0678</span>
585
+ <span><i class="fas fa-clock"></i> Tomorrow, 09:00 AM</span>
586
+ </div>
587
+ <span class="case-status status-pending">Bail Determination</span>
588
+ <p>Defendant charged with aggravated assault. Prosecution opposing bail.</p>
589
+
590
+ </div>
591
+
592
+ <!-- Hearing Card 2 -->
593
+ <div class="case-card">
594
+ <h4>Motion Hearing: Williams vs. County</h4>
595
+ <div class="case-meta">
596
+ <span><i class="fas fa-hashtag"></i> CV-2024-0345</span>
597
+ <span><i class="fas fa-clock"></i> Monday, 10:30 AM</span>
598
+ </div>
599
+ <span class="case-status status-in-progress">Motion to Dismiss</span>
600
+ <p>Civil rights case. Defendant has filed motion to dismiss for failure to state a claim.</p>
601
+
602
+ </div>
603
+ </div>
604
+ </section>
605
+ </main>
606
+ </div>
607
+
608
+ <!-- Chatbot Button -->
609
+ <div class="chatbot-btn" id="chatbotButton">
610
+ <i class="fas fa-robot"></i>
611
+ </div>
612
+
613
+ <script>
614
+ // --- User Profile Dropdown Logic ---
615
+ const userProfileMenu = document.getElementById('userProfileMenu');
616
+ const dropdownMenu = document.getElementById('dropdownMenu');
617
+ const headerLogoutBtn = document.getElementById('headerLogoutBtn');
618
+ const sidebarLogoutBtn = document.getElementById('logoutBtn'); // Keep existing backup
619
+
620
+ async function checkUserStatus() {
621
+ const token = localStorage.getItem('token');
622
+ if (!token) return;
623
+
624
+ try {
625
+ const response = await fetch('/api/user-status', {
626
+ headers: { 'Authorization': `Bearer ${token}` }
627
+ });
628
+ const status = await response.json();
629
+
630
+ const usageInfo = document.getElementById('usageInfo');
631
+ if (status.is_admin) {
632
+ usageInfo.style.display = 'none';
633
+ } else {
634
+ usageInfo.style.display = 'flex';
635
+ const remaining = Math.max(0, 2 - status.question_count);
636
+ document.getElementById('remainingCount').innerText = remaining;
637
+ }
638
+ } catch (err) {
639
+ console.error('Failed to fetch user status:', err);
640
+ }
641
+ }
642
+
643
+ checkUserStatus();
644
+
645
+ userProfileMenu.addEventListener('click', (e) => {
646
+ e.stopPropagation();
647
+ dropdownMenu.classList.toggle('show');
648
+ });
649
+
650
+ document.addEventListener('click', () => {
651
+ if (dropdownMenu.classList.contains('show')) {
652
+ dropdownMenu.classList.remove('show');
653
+ }
654
+ });
655
+
656
+ function performLogout() {
657
+ if (window.dashboardEntrance) {
658
+ window.dashboardEntrance.logout();
659
+ } else {
660
+ localStorage.removeItem('token');
661
+ window.location.href = "/role";
662
+ }
663
+ }
664
+
665
+ headerLogoutBtn.addEventListener('click', performLogout);
666
+
667
+ // Keep existing sidebar logout functional
668
+ if (sidebarLogoutBtn) {
669
+ sidebarLogoutBtn.addEventListener("click", function (e) {
670
+ e.preventDefault();
671
+ performLogout();
672
+ });
673
+ }
674
+
675
+ // Simplified chatbot functionality
676
+ document.getElementById("chatbotButton").addEventListener("click", function () {
677
+ // Direct navigation without alert
678
+ window.location.href = "/judgechatbot.html";
679
+ });
680
+
681
+ // Real-time updates (unchanged)
682
+ setInterval(function () {
683
+ const urgentMatters = document.querySelector('.stat-card:nth-child(4) p');
684
+ const randomUpdate = Math.floor(Math.random() * 3);
685
+ if (randomUpdate === 1) {
686
+ const current = parseInt(urgentMatters.textContent);
687
+ urgentMatters.textContent = current + 1;
688
+ urgentMatters.parentElement.style.animation = "pulse 0.5s";
689
+ setTimeout(() => {
690
+ urgentMatters.parentElement.style.animation = "";
691
+ }, 500);
692
+ }
693
+ }, 10000);
694
+ </script>
695
+ </body>
696
+
697
+ </html>
src/apps/templates/legalrights.html ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- Add this section below the Safety Section in your existing code -->
2
+ <section class="safety-tips" id="safety">
3
+ <div class="grid-container">
4
+ <div class="feature-card">
5
+ <h3>⚖️ Fundamental Legal Rights</h3>
6
+ <ul>
7
+ <li><i class="fas fa-balance-scale"></i> Right to equality (Article 14-18 of the Constitution)</li>
8
+ <li><i class="fas fa-university"></i> Right to education (Article 21A - Free and compulsory education)</li>
9
+ <li><i class="fas fa-gavel"></i> Right to legal aid under Article 39A</li>
10
+ <li><i class="fas fa-user-shield"></i> Protection from arrest in certain cases (Section 41A CrPC)</li>
11
+ </ul>
12
+ </div>
13
+
14
+ <div class="feature-card">
15
+ <h3>👩‍⚖️ Women's Legal Rights</h3>
16
+ <ul>
17
+ <li><i class="fas fa-venus"></i> Right to free legal aid under the Legal Services Authorities Act</li>
18
+ <li><i class="fas fa-home"></i> Right to residence (Protection of Women from Domestic Violence Act, 2005)</li>
19
+ <li><i class="fas fa-shield-alt"></i> Protection against sexual harassment at workplace (POSH Act, 2013)</li>
20
+ <li><i class="fas fa-exclamation-triangle"></i> Right to immediate police assistance under Zero FIR (CrPC 154)</li>
21
+ </ul>
22
+ </div>
23
+
24
+ <div class="feature-card">
25
+ <h3>🛑 Consumer Rights</h3>
26
+ <ul>
27
+ <li><i class="fas fa-receipt"></i> Right to be informed about products/services before purchase</li>
28
+ <li><i class="fas fa-shield-alt"></i> Right to seek redressal for unfair trade practices</li>
29
+ <li><i class="fas fa-handshake"></i> Right to consumer education and awareness</li>
30
+ <li><i class="fas fa-money-bill-wave"></i> Right to claim compensation for defective goods</li>
31
+ </ul>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Legal Resources Section -->
36
+ <div class="law-section">
37
+ <h2>Know Your Rights</h2>
38
+ <div class="quick-links">
39
+ <div class="safety-kit">
40
+ <p>✅ Keep a copy of important legal documents (Aadhaar, PAN, Passport)</p>
41
+ <p>✅ Know your rights under IPC, CrPC, and the Constitution</p>
42
+ <p>✅ Contact the National Legal Services Authority (NALSA) for legal aid</p>
43
+ <p>✅ Save helpline numbers: 1091 (Women), 14400 (Consumer complaints)</p>
44
+ </div>
45
+ <button class="safety-button" onclick="downloadLegalGuide()">
46
+ <i class="fas fa-download"></i>
47
+ Download Legal Rights Guide
48
+ </button>
49
+ </div>
50
+ </div>
51
+ <button class="back-button" onclick="goBack()">
52
+ <i class="fas fa-arrow-left"></i>
53
+ Back to Previous Page
54
+ </button>
55
+ </section>
56
+
57
+ <!-- CSS for Safety Section -->
58
+ <style>
59
+ .safety-tips {
60
+ position: relative;
61
+ padding: 8rem 5%;
62
+ background: #f9f9f9;
63
+ }
64
+
65
+ .safety-tips .grid-container {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
68
+ gap: 3rem;
69
+ }
70
+
71
+ .safety-tips .feature-card {
72
+ background: white;
73
+ border: 1px solid #ddd;
74
+ padding: 2.5rem;
75
+ border-radius: 12px;
76
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
77
+ }
78
+
79
+ .safety-tips .feature-card:hover {
80
+ transform: translateY(-5px);
81
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
82
+ }
83
+
84
+ .safety-tips h3 {
85
+ font-size: 1.5rem;
86
+ font-weight: 600;
87
+ margin-bottom: 1.5rem;
88
+ color: #333;
89
+ }
90
+
91
+ .safety-tips ul {
92
+ list-style: none;
93
+ padding: 0;
94
+ margin: 0;
95
+ }
96
+
97
+ .safety-tips li {
98
+ padding: 1rem 0;
99
+ border-bottom: 1px solid #eee;
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 1rem;
103
+ transition: transform 0.3s ease;
104
+ }
105
+
106
+ .safety-tips li:last-child {
107
+ border-bottom: none;
108
+ }
109
+
110
+ .safety-tips li:hover {
111
+ transform: translateX(8px);
112
+ }
113
+
114
+ .safety-tips i {
115
+ font-size: 1.1rem;
116
+ width: auto;
117
+ height: auto;
118
+ border-radius: 8px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ background: #e91e63;
123
+ color: white;
124
+ flex-shrink: 0;
125
+ transition: all 0.3s ease;
126
+ }
127
+
128
+ .safety-kit {
129
+ background: white;
130
+ border-radius: 12px;
131
+ padding: 2.5rem;
132
+ border: 1px solid #ddd;
133
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
134
+ margin: 4rem 0;
135
+ }
136
+
137
+ .safety-button {
138
+ background: #e91e63;
139
+ border: none;
140
+ padding: 1rem 2rem;
141
+ border-radius: 8px;
142
+ color: white;
143
+ font-weight: 600;
144
+ cursor: pointer;
145
+ transition: all 0.3s ease;
146
+ display: flex;
147
+ }
148
+
149
+ .safety-button:hover {
150
+ background: #d81b60;
151
+ }
152
+ .back-button {
153
+ background: #555;
154
+ border: none;
155
+ padding: 1rem 2rem;
156
+ border-radius: 8px;
157
+ color: white;
158
+ font-weight: 600;
159
+ cursor: pointer;
160
+ transition: all 0.3s ease;
161
+ display: flex;
162
+ align-items: center;
163
+ margin-top: 2rem;
164
+ }
165
+
166
+ @media (max-width: 768px) {
167
+ .safety-tips {
168
+ padding: 4rem 5%;
169
+ }
170
+ .safety-tips .feature-card {
171
+ padding: 1.75rem;
172
+ }
173
+ .safety-kit {
174
+ padding: 1.5rem;
175
+ }
176
+ }
177
+ </style>
178
+
179
+ <script>
180
+ function downloadLegalGuide() {
181
+ alert("Downloading Legal Rights Handbook...");
182
+ }
183
+ function goBack() {
184
+ window.history.back();
185
+ }
186
+ </script>
src/apps/templates/login.html ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Login - Law Bot</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
9
+ <style>
10
+ .auth-container {
11
+ max-width: 400px;
12
+ margin: 50px auto;
13
+ padding: 2rem;
14
+ background: rgba(255, 255, 255, 0.1);
15
+ backdrop-filter: blur(10px);
16
+ border-radius: 12px;
17
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
18
+ color: white;
19
+ }
20
+
21
+ .auth-container h2 {
22
+ text-align: center;
23
+ margin-bottom: 1.5rem;
24
+ }
25
+
26
+ .form-group {
27
+ margin-bottom: 1rem;
28
+ }
29
+
30
+ .form-group label {
31
+ display: block;
32
+ margin-bottom: 0.5rem;
33
+ }
34
+
35
+ .form-group input {
36
+ width: 100%;
37
+ padding: 0.75rem;
38
+ border: 1px solid rgba(255, 255, 255, 0.2);
39
+ border-radius: 6px;
40
+ background: rgba(0, 0, 0, 0.2);
41
+ color: white;
42
+ }
43
+
44
+ .btn {
45
+ width: 100%;
46
+ padding: 0.75rem;
47
+ background: #9b87f5;
48
+ color: white;
49
+ border: none;
50
+ border-radius: 6px;
51
+ cursor: pointer;
52
+ font-size: 1rem;
53
+ transition: background 0.3s;
54
+ }
55
+
56
+ .btn:hover {
57
+ background: #7e69d6;
58
+ }
59
+
60
+ .links {
61
+ margin-top: 1rem;
62
+ text-align: center;
63
+ font-size: 0.9rem;
64
+ }
65
+
66
+ .links a {
67
+ color: #D6BCFA;
68
+ text-decoration: none;
69
+ }
70
+
71
+ .links a:hover {
72
+ text-decoration: underline;
73
+ }
74
+
75
+ /* Phase 2: Portal Overlay */
76
+ #portalOverlay {
77
+ position: fixed;
78
+ top: 0;
79
+ left: 0;
80
+ width: 100%;
81
+ height: 100%;
82
+ background: rgba(10, 10, 15, 0.95);
83
+ backdrop-filter: blur(20px);
84
+ z-index: 9999;
85
+ display: none;
86
+ justify-content: center;
87
+ align-items: center;
88
+ flex-direction: column;
89
+ color: white;
90
+ opacity: 0;
91
+ transition: opacity 0.5s ease;
92
+ }
93
+
94
+ #portalOverlay.show {
95
+ display: flex;
96
+ opacity: 1;
97
+ }
98
+
99
+ .portal-pulse {
100
+ width: 80px;
101
+ height: 80px;
102
+ background: #9b87f5;
103
+ border-radius: 50%;
104
+ margin-bottom: 20px;
105
+ animation: portalPulse 2s infinite;
106
+ display: flex;
107
+ justify-content: center;
108
+ align-items: center;
109
+ box-shadow: 0 0 30px rgba(155, 135, 245, 0.5);
110
+ }
111
+
112
+ .portal-pulse svg {
113
+ width: 40px;
114
+ height: 40px;
115
+ color: white;
116
+ }
117
+
118
+ @keyframes portalPulse {
119
+ 0% {
120
+ transform: scale(0.95);
121
+ box-shadow: 0 0 0 0 rgba(155, 135, 245, 0.7);
122
+ }
123
+
124
+ 70% {
125
+ transform: scale(1.1);
126
+ box-shadow: 0 0 0 20px rgba(155, 135, 245, 0);
127
+ }
128
+
129
+ 100% {
130
+ transform: scale(0.95);
131
+ box-shadow: 0 0 0 0 rgba(155, 135, 245, 0);
132
+ }
133
+ }
134
+
135
+ .portal-message {
136
+ font-size: 1.5rem;
137
+ font-weight: 500;
138
+ letter-spacing: 1px;
139
+ text-align: center;
140
+ }
141
+
142
+ .portal-sub {
143
+ font-size: 0.9rem;
144
+ opacity: 0.6;
145
+ margin-top: 10px;
146
+ text-transform: uppercase;
147
+ letter-spacing: 3px;
148
+ }
149
+
150
+ .btn:disabled {
151
+ opacity: 0.7;
152
+ cursor: not-allowed;
153
+ background: #64748b;
154
+ }
155
+
156
+ .professional-footer {
157
+ margin-top: 2rem;
158
+ padding: 1.5rem 0;
159
+ text-align: center;
160
+ border-top: 1px solid rgba(155, 135, 245, 0.1);
161
+ color: rgba(255, 255, 255, 0.5);
162
+ font-size: 0.85rem;
163
+ width: 100%;
164
+ }
165
+
166
+ .professional-footer a {
167
+ color: #9b87f5;
168
+ text-decoration: none;
169
+ font-weight: 500;
170
+ }
171
+
172
+ .professional-footer a:hover {
173
+ text-decoration: underline;
174
+ }
175
+ </style>
176
+ </head>
177
+
178
+ <body class="dark">
179
+ <div id="portalOverlay">
180
+ <div class="portal-pulse">
181
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
182
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
183
+ </svg>
184
+ </div>
185
+ <div class="portal-message" id="portalMessage">INITIALIZING TERMINAL</div>
186
+ <div class="portal-sub">Secure Professional Access</div>
187
+ </div>
188
+
189
+ <div class="auth-container">
190
+ <h2>Login</h2>
191
+ <form id="loginForm">
192
+ <div class="form-group">
193
+ <label for="username">Username</label>
194
+ <input type="text" id="username" required>
195
+ </div>
196
+ <div class="form-group">
197
+ <label for="password">Password</label>
198
+ <input type="password" id="password" required>
199
+ </div>
200
+ <button type="submit" class="btn">Login</button>
201
+ </form>
202
+ <div class="links">
203
+ <a href="/register" id="registerLink">Don't have an account? Register</a><br>
204
+ <a href="/forgot-password">Forgot Password?</a>
205
+ </div>
206
+ <footer class="professional-footer">
207
+ &copy; 2026 Law Bot AI. All Rights Reserved.
208
+ <br>
209
+ Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
210
+ </footer>
211
+ </div>
212
+
213
+ <script>
214
+ const urlParams = new URLSearchParams(window.location.search);
215
+ const role = urlParams.get('role');
216
+
217
+ if (role) {
218
+ document.getElementById('registerLink').href = `/register?role=${role}`;
219
+ }
220
+
221
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
222
+ e.preventDefault();
223
+ const username = document.getElementById('username').value;
224
+ const password = document.getElementById('password').value;
225
+
226
+ try {
227
+ const response = await fetch('/api/login', {
228
+ method: 'POST',
229
+ headers: { 'Content-Type': 'application/json' },
230
+ body: JSON.stringify({ username, password, role })
231
+ });
232
+
233
+ const loginBtn = document.querySelector('.btn');
234
+ const originalBtnText = loginBtn.innerText;
235
+ loginBtn.disabled = true;
236
+ loginBtn.innerText = 'Verifying...';
237
+
238
+ const data = await response.json();
239
+ if (response.ok) {
240
+ localStorage.setItem('token', data.access_token);
241
+
242
+ const rolePages = {
243
+ 'Judge': 'judgedashboard.html',
244
+ 'Advocate/Lawyer': 'advocatedashboard.html',
245
+ 'Woman': 'woman.html',
246
+ 'Citizen': 'citizen.html',
247
+ 'Student': 'studentdashboard.html',
248
+ 'Minor': 'minor.html'
249
+ };
250
+
251
+ const roleMessages = {
252
+ 'Judge': 'Initializing Judicial Terminal...',
253
+ 'Advocate/Lawyer': 'Welcome back, Counselor. Securing portal...',
254
+ 'Woman': 'Welcome to Shakti Empowerment Hub...',
255
+ 'Citizen': 'Accessing Law Bot Public Terminal...',
256
+ 'Student': 'Setting up your legal study environment...',
257
+ 'Minor': 'Hello! Getting Law Bot ready for you...'
258
+ };
259
+
260
+ let userRole = data.role;
261
+ if (data.role === 'Admin' && role) {
262
+ userRole = role;
263
+ } else if (!userRole && role) {
264
+ userRole = role;
265
+ }
266
+
267
+ // Show professional portal overlay
268
+ const overlay = document.getElementById('portalOverlay');
269
+ const msgEl = document.getElementById('portalMessage');
270
+ msgEl.innerText = roleMessages[userRole] || 'Access Granted. Entering Portal...';
271
+ overlay.classList.add('show');
272
+
273
+ // Brief delay for the aesthetic "Secure Entry" feel before redirect
274
+ setTimeout(() => {
275
+ if (userRole && rolePages[userRole]) {
276
+ window.location.href = rolePages[userRole];
277
+ } else {
278
+ window.location.href = '/';
279
+ }
280
+ }, 1200);
281
+
282
+ } else {
283
+ loginBtn.disabled = false;
284
+ loginBtn.innerText = originalBtnText;
285
+ alert(data.detail || 'Login failed');
286
+ }
287
+ } catch (error) {
288
+ console.error('Error:', error);
289
+ alert('An error occurred');
290
+ }
291
+ });
292
+ </script>
293
+ </body>
294
+
295
+ </html>
src/apps/templates/minor.html ADDED
@@ -0,0 +1,897 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Law bot</title>
8
+ <link rel="stylesheet" href="/static/css/entrance.css">
9
+ <script src="/static/js/entrance.js" defer></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="/static/css/modern-chat.css">
12
+ <style>
13
+ :root {
14
+ --primary-purple: #9b87f5;
15
+ --dark-purple: #1A1F2C;
16
+ --light-purple: #D6BCFA;
17
+ --neutral-gray: #8E9196;
18
+ --light-gray: #C8C8C9;
19
+ --off-white: #eee;
20
+ }
21
+
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ font-family: 'Inter', sans-serif;
27
+ }
28
+
29
+ body {
30
+ transition: background-color 0.3s, color 0.3s;
31
+ }
32
+
33
+ body.dark {
34
+ color: var(--off-white);
35
+ }
36
+
37
+ body.light {
38
+ color: var(--dark-purple);
39
+ }
40
+
41
+ .container {
42
+ max-width: 800px;
43
+ margin: 0 auto;
44
+ padding: 2rem;
45
+ min-height: 100vh;
46
+ display: flex;
47
+ flex-direction: column;
48
+ }
49
+
50
+ .header {
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ margin-bottom: 2rem;
55
+ }
56
+
57
+ .header-right {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 1rem;
61
+ }
62
+
63
+
64
+ .app-title {
65
+ margin-left: 40px;
66
+ font-size: 2rem;
67
+ font-weight: 600;
68
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
69
+ -webkit-background-clip: text;
70
+ background-clip: text;
71
+ color: transparent;
72
+ }
73
+
74
+ .theme-toggle {
75
+ background: none;
76
+ border: none;
77
+ cursor: pointer;
78
+ padding: 0.5rem;
79
+ color: var(--primary-purple);
80
+ transition: opacity 0.3s;
81
+ }
82
+
83
+ .theme-toggle:hover {
84
+ opacity: 0.8;
85
+ }
86
+
87
+ .theme-toggle svg {
88
+ width: 24px;
89
+ height: 24px;
90
+ }
91
+
92
+ .chat-container {
93
+ flex-grow: 1;
94
+ overflow-y: auto;
95
+ margin-bottom: 2rem;
96
+ padding: 1rem;
97
+ }
98
+
99
+ .message {
100
+ margin-bottom: 1.5rem;
101
+ padding: 1rem;
102
+ border-radius: 8px;
103
+ animation: fadeIn 0.3s ease-in;
104
+ }
105
+
106
+ .user-message {
107
+ background-color: rgba(155, 135, 245, 0.1);
108
+ }
109
+
110
+ .ai-message {
111
+ background-color: rgba(255, 255, 255, 0.05);
112
+ border: 1px solid rgba(200, 200, 201, 0.2);
113
+ }
114
+
115
+ body.light .user-message {
116
+ background-color: var(--off-white);
117
+ }
118
+
119
+ body.light .ai-message {
120
+ background-color: #ffffff;
121
+ border: 1px solid var(--light-gray);
122
+ }
123
+
124
+ .message-header {
125
+ display: flex;
126
+ align-items: center;
127
+ margin-bottom: 0.5rem;
128
+ font-weight: 500;
129
+ }
130
+
131
+
132
+ .sidebar-welcome {
133
+ padding: 1.5rem;
134
+ border-bottom: 1px solid rgba(200, 200, 201, 0.15);
135
+ margin-bottom: 1.5rem;
136
+ }
137
+
138
+ .user-icon,
139
+ .ai-icon {
140
+ width: 24px;
141
+ height: 24px;
142
+ border-radius: 50%;
143
+ margin-right: 0.5rem;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ font-size: 12px;
148
+ }
149
+
150
+ .user-icon {
151
+ background-color: var(--primary-purple);
152
+ color: white;
153
+ }
154
+
155
+ .ai-icon {
156
+ background-color: var(--light-purple);
157
+ color: var(--dark-purple);
158
+ }
159
+
160
+ .input-container {
161
+ position: fixed;
162
+ bottom: 0;
163
+ left: 0;
164
+ right: 0;
165
+ padding: 1rem;
166
+ background-color: var(--input-bg);
167
+ border-top: 1px solid var(--sidebar-border);
168
+ }
169
+
170
+ body.light .input-container {
171
+ background-color: #ffffff;
172
+ border-top: 1px solid var(--light-gray);
173
+ }
174
+
175
+ .input-wrapper {
176
+ max-width: 800px;
177
+ margin: 0 auto;
178
+ position: relative;
179
+ }
180
+
181
+ #messageInput {
182
+ width: 100%;
183
+ padding: 1rem;
184
+ padding-right: 3rem;
185
+ border: 1px solid rgba(200, 200, 201, 0.3);
186
+ border-radius: 8px;
187
+ font-size: 1rem;
188
+ outline: none;
189
+ transition: border-color 0.3s;
190
+ background-color: transparent;
191
+ color: inherit;
192
+ }
193
+
194
+ body.light #messageInput {
195
+ border: 1px solid var(--light-gray);
196
+ }
197
+
198
+ #messageInput:focus {
199
+ border-color: var(--primary-purple);
200
+ }
201
+
202
+ #sendButton {
203
+ position: absolute;
204
+ right: 0.5rem;
205
+ top: 50%;
206
+ transform: translateY(-50%);
207
+ background: none;
208
+ border: none;
209
+ cursor: pointer;
210
+ padding: 0.5rem;
211
+ color: var(--primary-purple);
212
+ opacity: 0.8;
213
+ transition: opacity 0.3s;
214
+ }
215
+
216
+ #sendButton:hover {
217
+ opacity: 1;
218
+ }
219
+
220
+ .typing-indicator {
221
+ padding: 1rem;
222
+ color: var(--neutral-gray);
223
+ font-style: italic;
224
+ display: none;
225
+ }
226
+
227
+ .welcome-title {
228
+ margin: 6rem 0 0.5rem 0;
229
+ text-align: center;
230
+ color: var(--neutral-gray);
231
+ padding: 1rem;
232
+ font-size: 1.24rem;
233
+ animation: fadeIn 0.3s ease-in;
234
+ }
235
+
236
+ .welcome-subtitle {
237
+ line-height: 1.6;
238
+ font-size: 1.1rem;
239
+ color: var(--neutral-gray);
240
+ max-width: 900px;
241
+ margin: 0 auto;
242
+ font-weight: 800;
243
+ color: var(--primary-purple);
244
+
245
+ }
246
+
247
+ .sidebar-welcome {
248
+ margin-bottom: 2rem;
249
+ padding: 0 1rem;
250
+ border-bottom: none;
251
+ }
252
+
253
+ .role-selection-link {
254
+ display: inline-flex;
255
+ align-items: center;
256
+ gap: initial;
257
+ color: var(--light-purple);
258
+ text-decoration: none;
259
+ margin-top: auto;
260
+ padding: auto;
261
+ transition: opacity 0.3s ease;
262
+ position: absolute;
263
+ top: 550px;
264
+ left: 30px;
265
+
266
+ }
267
+
268
+ .role-selection-link:hover {
269
+ opacity: 0.8;
270
+ }
271
+
272
+ .role-selection-link svg {
273
+ width: 16px;
274
+ height: 16px;
275
+ stroke: currentColor;
276
+ }
277
+
278
+ /* Add this media query for mobile responsiveness */
279
+ @media (max-width: 480px) {
280
+ .welcome-message p {
281
+ font-size: 1rem;
282
+ padding: 0 1rem;
283
+ }
284
+ }
285
+
286
+ @keyframes fadeIn {
287
+ from {
288
+ opacity: 0;
289
+ transform: translateY(10px);
290
+ }
291
+
292
+ to {
293
+ opacity: 1;
294
+ transform: translateY(0);
295
+ }
296
+ }
297
+
298
+ .connection-status {
299
+ position: fixed;
300
+ top: 1rem;
301
+ right: 1rem;
302
+ padding: 0.5rem 1rem;
303
+ border-radius: 4px;
304
+ font-size: 0.875rem;
305
+ display: none;
306
+ }
307
+
308
+ .connection-status.connected {
309
+ background-color: #4ade80;
310
+ color: white;
311
+ }
312
+
313
+ .connection-status.disconnected {
314
+ background-color: #ef4444;
315
+ color: white;
316
+ }
317
+
318
+ /* Styling for formatted text */
319
+ .message-content {
320
+ line-height: 1.5;
321
+ }
322
+
323
+ .message-content h1,
324
+ .message-content h2,
325
+ .message-content h3 {
326
+ margin: 1rem 0 0.5rem;
327
+ font-weight: 600;
328
+ }
329
+
330
+ .message-content h1 {
331
+ font-size: 1.5rem;
332
+ }
333
+
334
+ .message-content h2 {
335
+ font-size: 1.25rem;
336
+ }
337
+
338
+ .message-content h3 {
339
+ font-size: 1.1rem;
340
+ }
341
+
342
+ .message-content ul,
343
+ .message-content ol {
344
+ margin-left: 1.5rem;
345
+ margin-bottom: 1rem;
346
+ }
347
+
348
+ .message-content a {
349
+ color: var(--primary-purple);
350
+ text-decoration: none;
351
+ }
352
+
353
+ .message-content a:hover {
354
+ text-decoration: underline;
355
+ }
356
+
357
+ .usage-indicator {
358
+ padding: 4px 12px;
359
+ background: rgba(155, 135, 245, 0.1);
360
+ border: 1px solid rgba(155, 135, 245, 0.3);
361
+ border-radius: 20px;
362
+ font-size: 0.8rem;
363
+ color: var(--primary-purple);
364
+ font-weight: 500;
365
+ display: none;
366
+ /* Hidden by default for Admins */
367
+ }
368
+ </style>
369
+ </head>
370
+
371
+ <button class="sidebar-floating-toggle" id="floatingToggle">
372
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
373
+ <path d="M3 12h18M3 6h18M3 18h18" />
374
+ </svg>
375
+ </button>
376
+
377
+ <aside class="sidebar" id="sidebar">
378
+ <div class="sidebar-header">
379
+ <button class="new-chat-btn" id="newChatBtn">
380
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
381
+ <path d="M12 5v14M5 12h14" />
382
+ </svg>
383
+ New Chat
384
+ </button>
385
+ <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
386
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
387
+ <path d="M15 18l-6-6 6-6" />
388
+ </svg>
389
+ </button>
390
+ </div>
391
+ <div class="sidebar-title">Recent Learning</div>
392
+ <div class="history-list" id="historyList">
393
+ <!-- Recent prompts will appear here -->
394
+ </div>
395
+
396
+ <div class="perspective-container" style="display: none;">
397
+ <div class="sidebar-title">Answer Perspective</div>
398
+ <div class="perspective-list">
399
+ <div class="perspective-option active" data-role="Minor">🧒 Minor</div>
400
+ </div>
401
+ </div>
402
+
403
+
404
+ </aside>
405
+
406
+ <!-- Existing messages will be added here dynamically -->
407
+ <div class="container">
408
+ <header class="header">
409
+ <h1 class="app-title">Law Bot (Minor)</h1>
410
+ <div class="header-right">
411
+ <div class="usage-indicator" id="usageIndicator">
412
+ Questions Remaining: <span id="remainingCount">--</span>
413
+ </div>
414
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
415
+ <!-- Icon injected by JS -->
416
+ </button>
417
+ <div class="user-profile-menu" id="userProfileMenu">
418
+ <div class="profile-icon-btn">
419
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
420
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
421
+ <circle cx="12" cy="7" r="4"></circle>
422
+ </svg>
423
+ </div>
424
+ <div class="dropdown-menu" id="dropdownMenu">
425
+ <div class="dropdown-item role-info">
426
+ Role: Minor
427
+ </div>
428
+ <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
429
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
430
+ stroke-width="2">
431
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
432
+ <polyline points="16 17 21 12 16 7"></polyline>
433
+ <line x1="21" y1="12" x2="9" y2="12"></line>
434
+ </svg>
435
+ Logout
436
+ </a>
437
+ </div>
438
+ </div>
439
+ </div>
440
+ </header>
441
+ <div class="welcome-title">Welcome, Future Leader! 👦 </div>
442
+ <div class="welcome-subtitle">Let’s learn about laws in a fun, easy way! What would you like to understand
443
+ today?</div>
444
+
445
+
446
+
447
+ <div class="chat-container" id="chatContainer">
448
+ <div class="typing-indicator" id="typingIndicator">
449
+ <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--minor-color)" stroke-width="2">
450
+ <circle cx="12" cy="12" r="10" />
451
+ <path d="M8 14s1.5 2 4 2 4-2 4-2" />
452
+ <line x1="9" y1="9" x2="9.01" y2="9" />
453
+ <line x1="15" y1="9" x2="15.01" y2="9" />
454
+ </svg>
455
+ <span>Searching simple answers for you...</span>
456
+ </div>
457
+ </div>
458
+ <div class="input-container">
459
+ <div class="input-wrapper">
460
+ <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
461
+ <button id="sendButton">
462
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
463
+ stroke-linecap="round" stroke-linejoin="round">
464
+ <line x1="22" y1="2" x2="11" y2="13"></line>
465
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
466
+ </svg>
467
+ </button>
468
+ </div>
469
+ </div>
470
+ <footer class="professional-footer">
471
+ &copy; 2026 Law Bot AI. All Rights Reserved.
472
+ <br>
473
+ Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
474
+ </footer>
475
+ </div>
476
+ <div class="connection-status" id="connectionStatus"></div>
477
+
478
+ <script>
479
+ let ws;
480
+ const chatContainer = document.getElementById('chatContainer');
481
+ const messageInput = document.getElementById('messageInput');
482
+ const sendButton = document.getElementById('sendButton');
483
+ const typingIndicator = document.getElementById('typingIndicator');
484
+ const connectionStatus = document.getElementById('connectionStatus');
485
+ const sidebar = document.getElementById('sidebar');
486
+
487
+ let currentUserMessage = '';
488
+ let currentAiMessage = '';
489
+ let currentAiMessageElement = null;
490
+ let currentCaseId = crypto.randomUUID();
491
+ const activeRole = 'Minor'; // LOCKED ROLE
492
+ let userLimitReached = false;
493
+
494
+ async function checkUserStatus() {
495
+ const token = localStorage.getItem('token');
496
+ if (!token) return;
497
+
498
+ try {
499
+ const response = await fetch('/api/user-status', {
500
+ headers: { 'Authorization': `Bearer ${token}` }
501
+ });
502
+ const status = await response.json();
503
+
504
+ const indicator = document.getElementById('usageIndicator');
505
+ if (status.is_admin) {
506
+ indicator.style.display = 'none';
507
+ } else {
508
+ indicator.style.display = 'block';
509
+ const remaining = Math.max(0, 2 - status.question_count);
510
+ document.getElementById('remainingCount').innerText = remaining;
511
+ if (remaining <= 0) {
512
+ userLimitReached = true;
513
+ }
514
+ }
515
+ } catch (err) {
516
+ console.error('Failed to fetch user status:', err);
517
+ }
518
+ }
519
+
520
+ function formatText(text) {
521
+ // Extract references for Evidence Box
522
+ const references = [];
523
+ const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
524
+ let match;
525
+ while ((match = refRegex.exec(text)) !== null) {
526
+ references.push({ title: match[1], pages: match[2] });
527
+ }
528
+
529
+ let formatted = text
530
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
531
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
532
+ .replace(/### (.*?)\n/g, "<h3>$1</h3>")
533
+ .replace(/## (.*?)\n/g, "<h2>$1</h2>")
534
+ .replace(/# (.*?)\n/g, "<h1>$1</h1>")
535
+ .replace(/\n/g, "<br>")
536
+ .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
537
+ .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
538
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
539
+
540
+ // Add Evidence Box if references found
541
+ if (references.length > 0) {
542
+ let evidenceHtml = `<div class="evidence-box">
543
+ <div class="evidence-title">
544
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
545
+ Where I found this
546
+ </div>`;
547
+ references.forEach(ref => {
548
+ evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
549
+ });
550
+ evidenceHtml += `</div>`;
551
+ formatted += evidenceHtml;
552
+ }
553
+ return formatted;
554
+ }
555
+
556
+ function connectWebSocket() {
557
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
558
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
559
+
560
+ ws.onopen = () => {
561
+ console.log('Connected to WebSocket');
562
+ connectionStatus.textContent = 'Connected';
563
+ connectionStatus.className = 'connection-status connected';
564
+ connectionStatus.style.display = 'block';
565
+ setTimeout(() => {
566
+ connectionStatus.style.display = 'none';
567
+ }, 3000);
568
+ };
569
+
570
+ ws.onclose = () => {
571
+ console.log('Disconnected from WebSocket');
572
+ connectionStatus.textContent = 'Trying to reconnect...';
573
+ connectionStatus.className = 'connection-status disconnected';
574
+ connectionStatus.style.display = 'block';
575
+
576
+ // Exponential backoff for reconnection
577
+ const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
578
+ window._reconnectCount = (window._reconnectCount || 0) + 1;
579
+
580
+ setTimeout(() => {
581
+ reconnectWebSocket();
582
+ loadHistory();
583
+ }, backoff);
584
+ };
585
+
586
+ ws.onerror = (error) => {
587
+ console.error('WebSocket Error:', error);
588
+ };
589
+
590
+
591
+ ws.onmessage = (event) => {
592
+ console.log('Received message:', event.data);
593
+
594
+ // ✅ Hide typing indicator on response
595
+ typingIndicator.style.display = 'none';
596
+
597
+ if (event.data === '[DONE]') {
598
+ // Save complete chat interaction
599
+ saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
600
+ checkUserStatus(); // Update remaining count
601
+ return;
602
+ }
603
+
604
+ // Handle usage limit blocks from backend
605
+ if (event.data.includes("Free usage limit reached")) {
606
+ typingIndicator.style.display = 'none';
607
+ userLimitReached = true;
608
+ checkUserStatus();
609
+ }
610
+
611
+ if (!currentAiMessageElement) {
612
+ currentAiMessage = event.data;
613
+ addMessage(currentAiMessage, 'ai');
614
+ } else {
615
+ currentAiMessage += event.data;
616
+ const formattedMessage = formatText(currentAiMessage);
617
+ currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
618
+ }
619
+ };
620
+
621
+ // ✅ Scroll fix (move into sendMessage function if needed)
622
+ chatContainer.scrollTop = chatContainer.scrollHeight;
623
+ }
624
+
625
+ function reconnectWebSocket() {
626
+ console.log('Attempting to reconnect...');
627
+ connectWebSocket();
628
+ loadHistory();
629
+ }
630
+
631
+ function addMessage(content, type) {
632
+ if (type === 'user') {
633
+ currentUserMessage = content;
634
+ currentAiMessage = '';
635
+ currentAiMessageElement = null;
636
+ }
637
+
638
+ const messageDiv = document.createElement('div');
639
+ messageDiv.className = `message ${type}-message`;
640
+
641
+ const header = document.createElement('div');
642
+ header.className = 'message-header';
643
+
644
+ const icon = document.createElement('div');
645
+ icon.className = `${type}-icon`;
646
+ icon.textContent = type === 'user' ? 'U' : 'L';
647
+
648
+ const name = document.createElement('span');
649
+ name.textContent = type === 'user' ? 'You' : 'Law Bot';
650
+
651
+ header.appendChild(icon);
652
+ header.appendChild(name);
653
+
654
+ const text = document.createElement('div');
655
+ text.className = 'message-content';
656
+ text.innerHTML = type === 'ai' ? formatText(content) : content;
657
+
658
+ messageDiv.appendChild(header);
659
+ messageDiv.appendChild(text);
660
+ chatContainer.appendChild(messageDiv);
661
+ chatContainer.scrollTop = chatContainer.scrollHeight;
662
+
663
+ if (type === 'ai') {
664
+ currentAiMessageElement = messageDiv;
665
+ }
666
+ }
667
+
668
+ function sendMessage() {
669
+ const message = messageInput.value.trim();
670
+ if (!message) return;
671
+
672
+ if (userLimitReached) {
673
+ addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
674
+ return;
675
+ }
676
+
677
+ if (!activeRole) {
678
+ addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
679
+ return;
680
+ }
681
+
682
+ if (ws.readyState === WebSocket.OPEN) {
683
+ addMessage(message, 'user');
684
+ ws.send(message);
685
+ messageInput.value = '';
686
+
687
+ // ✅ Show typing indicator after sending
688
+ typingIndicator.style.display = 'block';
689
+ chatContainer.scrollTop = chatContainer.scrollHeight;
690
+ }
691
+ }
692
+ const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
693
+ if (isAtBottom) {
694
+ chatContainer.scrollTop = chatContainer.scrollHeight;
695
+ }
696
+
697
+
698
+ const historyList = document.getElementById('historyList');
699
+
700
+ async function loadHistory() {
701
+ const token = localStorage.getItem('token');
702
+ if (!token) return;
703
+
704
+ try {
705
+ const response = await fetch(`/api/interactions?role=${activeRole}`, {
706
+ headers: { 'Authorization': `Bearer ${token}` }
707
+ });
708
+ const interactions = await response.json();
709
+ historyList.innerHTML = '';
710
+ interactions.forEach(item => {
711
+ const div = document.createElement('div');
712
+ div.className = 'history-item';
713
+
714
+ // Readable preview: first sentence or truncated query
715
+ const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
716
+
717
+ div.innerHTML = `
718
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
719
+ <span>${preview}</span>
720
+ <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
721
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
722
+ </button>
723
+ `;
724
+ div.onclick = () => loadConversation(item.case_id);
725
+ historyList.appendChild(div);
726
+ });
727
+ } catch (err) {
728
+ console.error('Failed to load history:', err);
729
+ }
730
+ }
731
+
732
+ async function loadConversation(caseId) {
733
+ const token = localStorage.getItem('token');
734
+ try {
735
+ const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
736
+ headers: { 'Authorization': `Bearer ${token}` }
737
+ });
738
+ const thread = await response.json();
739
+
740
+ currentCaseId = caseId;
741
+ chatContainer.innerHTML = ''; // Clear window
742
+
743
+ thread.forEach(msg => {
744
+ addMessage(msg.query, 'user');
745
+ // Render AI Response immediately
746
+ const aiMsgDiv = document.createElement('div');
747
+ aiMsgDiv.className = 'message ai-message';
748
+ aiMsgDiv.innerHTML = `
749
+ <div class="message-header">
750
+ <div class="ai-icon">L</div>
751
+ <span>Law Bot</span>
752
+ </div>
753
+ <div class="message-content">${formatText(msg.response)}</div>
754
+ `;
755
+ chatContainer.appendChild(aiMsgDiv);
756
+ });
757
+ chatContainer.scrollTop = chatContainer.scrollHeight;
758
+ } catch (err) {
759
+ console.error('Failed to load thread:', err);
760
+ }
761
+ }
762
+
763
+ async function deleteConversation(caseId) {
764
+ const token = localStorage.getItem('token');
765
+ try {
766
+ await fetch(`/api/interactions/${caseId}`, {
767
+ method: 'DELETE',
768
+ headers: { 'Authorization': `Bearer ${token}` }
769
+ });
770
+ if (currentCaseId === caseId) {
771
+ newChat();
772
+ } else {
773
+ loadHistory();
774
+ }
775
+ } catch (err) {
776
+ console.error('Failed to delete:', err);
777
+ }
778
+ }
779
+
780
+ function newChat() {
781
+ currentCaseId = crypto.randomUUID();
782
+ chatContainer.innerHTML = '';
783
+ messageInput.value = '';
784
+ messageInput.focus();
785
+ loadHistory();
786
+ }
787
+
788
+ // ✅ Modified: Uses Bearer token
789
+ const saveChatInteraction = async (caseId, query, response) => {
790
+ const token = localStorage.getItem('token');
791
+ if (!token) return;
792
+
793
+ try {
794
+ await fetch('/api/save-interaction', {
795
+ method: 'POST',
796
+ headers: {
797
+ 'Content-Type': 'application/json',
798
+ 'Authorization': `Bearer ${token}`
799
+ },
800
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
801
+ });
802
+ loadHistory(); // Refresh sidebar history
803
+ } catch (error) {
804
+ console.error('Error saving chat:', error);
805
+ }
806
+ };
807
+
808
+ sendButton.addEventListener('click', sendMessage);
809
+ messageInput.addEventListener('keypress', (e) => {
810
+ if (e.key === 'Enter') {
811
+ sendMessage();
812
+ }
813
+ });
814
+
815
+ // --- User Profile Dropdown ---
816
+ const userProfileMenu = document.getElementById('userProfileMenu');
817
+ const dropdownMenu = document.getElementById('dropdownMenu');
818
+ const headerLogoutBtn = document.getElementById('headerLogoutBtn');
819
+
820
+ userProfileMenu.addEventListener('click', (e) => {
821
+ e.stopPropagation();
822
+ dropdownMenu.classList.toggle('show');
823
+ });
824
+
825
+ document.addEventListener('click', () => {
826
+ if (dropdownMenu.classList.contains('show')) {
827
+ dropdownMenu.classList.remove('show');
828
+ }
829
+ });
830
+
831
+ headerLogoutBtn.addEventListener('click', (e) => {
832
+ e.preventDefault();
833
+ if (window.dashboardEntrance) {
834
+ window.dashboardEntrance.logout();
835
+ } else {
836
+ localStorage.removeItem('token');
837
+ window.location.href = '/role';
838
+ }
839
+ });
840
+
841
+ // Theme toggle
842
+ const themeToggle = document.getElementById('themeToggle');
843
+ const body = document.body;
844
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
845
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
846
+
847
+ function updateTheme(isDark) {
848
+ if (isDark) {
849
+ body.classList.add('dark');
850
+ body.classList.remove('light');
851
+ themeToggle.innerHTML = moonIcon;
852
+ localStorage.setItem('theme', 'dark');
853
+ } else {
854
+ body.classList.add('light');
855
+ body.classList.remove('dark');
856
+ themeToggle.innerHTML = sunIcon;
857
+ localStorage.setItem('theme', 'light');
858
+ }
859
+ }
860
+
861
+ themeToggle.addEventListener('click', () => {
862
+ const isNowDark = !body.classList.contains('dark');
863
+ updateTheme(isNowDark);
864
+ });
865
+
866
+ // Initialize Theme
867
+ const savedTheme = localStorage.getItem('theme') || 'dark';
868
+ updateTheme(savedTheme === 'dark');
869
+
870
+ // Sidebar Collapse Logic
871
+ document.getElementById('collapseSidebar').onclick = () => {
872
+ document.body.classList.add('sidebar-collapsed');
873
+ };
874
+ document.getElementById('floatingToggle').onclick = () => {
875
+ document.body.classList.remove('sidebar-collapsed');
876
+ };
877
+ document.getElementById('newChatBtn').onclick = newChat;
878
+
879
+ // Perspective Selection
880
+ document.querySelectorAll('.perspective-option').forEach(opt => {
881
+ opt.onclick = () => {
882
+ document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
883
+ opt.classList.add('active');
884
+ activeRole = opt.dataset.role;
885
+ localStorage.setItem('activeRole', activeRole);
886
+ // No session reset needed, just role update for next message
887
+ };
888
+ });
889
+
890
+ checkUserStatus();
891
+ connectWebSocket();
892
+ loadHistory();
893
+
894
+ </script>
895
+ </body>
896
+
897
+ </html>
src/apps/templates/register.html ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Register - Law Bot</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
9
+ <style>
10
+ /* Reuse styles from login.html */
11
+ .auth-container {
12
+ max-width: 400px;
13
+ margin: 50px auto;
14
+ padding: 2rem;
15
+ background: rgba(255, 255, 255, 0.1);
16
+ backdrop-filter: blur(10px);
17
+ border-radius: 12px;
18
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
19
+ color: white;
20
+ }
21
+
22
+ .auth-container h2 {
23
+ text-align: center;
24
+ margin-bottom: 1.5rem;
25
+ }
26
+
27
+ .form-group {
28
+ margin-bottom: 1rem;
29
+ }
30
+
31
+ .form-group label {
32
+ display: block;
33
+ margin-bottom: 0.5rem;
34
+ }
35
+
36
+ .form-group input {
37
+ width: 100%;
38
+ padding: 0.75rem;
39
+ border: 1px solid rgba(255, 255, 255, 0.2);
40
+ border-radius: 6px;
41
+ background: rgba(0, 0, 0, 0.2);
42
+ color: white;
43
+ }
44
+
45
+ .btn {
46
+ width: 100%;
47
+ padding: 0.75rem;
48
+ background: #9b87f5;
49
+ color: white;
50
+ border: none;
51
+ border-radius: 6px;
52
+ cursor: pointer;
53
+ font-size: 1rem;
54
+ transition: background 0.3s;
55
+ }
56
+
57
+ .btn:hover {
58
+ background: #7e69d6;
59
+ }
60
+
61
+ .links {
62
+ margin-top: 1rem;
63
+ text-align: center;
64
+ font-size: 0.9rem;
65
+ }
66
+
67
+ .links a {
68
+ color: #D6BCFA;
69
+ text-decoration: none;
70
+ }
71
+
72
+ .links a:hover {
73
+ text-decoration: underline;
74
+ }
75
+ </style>
76
+ </head>
77
+
78
+ <body class="dark">
79
+ <div class="auth-container">
80
+ <h2>Register</h2>
81
+ <form id="registerForm">
82
+ <div class="form-group">
83
+ <label for="username">Username</label>
84
+ <input type="text" id="username" required>
85
+ </div>
86
+ <div class="form-group">
87
+ <label for="email">Email</label>
88
+ <input type="email" id="email" required>
89
+ </div>
90
+ <div class="form-group">
91
+ <label for="password">Password</label>
92
+ <input type="password" id="password" required>
93
+ </div>
94
+ <input type="hidden" id="role">
95
+ <button type="submit" class="btn">Register</button>
96
+ </form>
97
+ <div class="links">
98
+ <a href="/login" id="loginLink">Already have an account? Login</a>
99
+ </div>
100
+ </div>
101
+
102
+ <script>
103
+ const urlParams = new URLSearchParams(window.location.search);
104
+ const role = urlParams.get('role');
105
+ if (role) {
106
+ document.getElementById('role').value = role;
107
+ document.getElementById('loginLink').href = `/login?role=${role}`;
108
+ }
109
+
110
+ document.getElementById('registerForm').addEventListener('submit', async (e) => {
111
+ e.preventDefault();
112
+ const username = document.getElementById('username').value;
113
+ const email = document.getElementById('email').value;
114
+ const password = document.getElementById('password').value;
115
+ const roleValue = document.getElementById('role').value;
116
+
117
+ try {
118
+ const response = await fetch('/api/register', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify({ username, email, password, role: roleValue })
122
+ });
123
+
124
+ const data = await response.json();
125
+ if (response.ok) {
126
+ alert('Registration successful! Please login.');
127
+ window.location.href = `/login?role=${roleValue}`;
128
+ } else {
129
+ alert(data.detail || 'Registration failed');
130
+ }
131
+ } catch (error) {
132
+ console.error('Error:', error);
133
+ alert('An error occurred');
134
+ }
135
+ });
136
+ </script>
137
+ </body>
138
+
139
+ </html>
src/apps/templates/reset_password.html ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Reset Password - Law Bot</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
9
+ <style>
10
+ .auth-container {
11
+ max-width: 400px;
12
+ margin: 50px auto;
13
+ padding: 2rem;
14
+ background: rgba(255, 255, 255, 0.1);
15
+ backdrop-filter: blur(10px);
16
+ border-radius: 12px;
17
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
18
+ color: white;
19
+ }
20
+
21
+ .auth-container h2 {
22
+ text-align: center;
23
+ margin-bottom: 1.5rem;
24
+ }
25
+
26
+ .form-group {
27
+ margin-bottom: 1rem;
28
+ }
29
+
30
+ .form-group label {
31
+ display: block;
32
+ margin-bottom: 0.5rem;
33
+ }
34
+
35
+ .form-group input {
36
+ width: 100%;
37
+ padding: 0.75rem;
38
+ border: 1px solid rgba(255, 255, 255, 0.2);
39
+ border-radius: 6px;
40
+ background: rgba(0, 0, 0, 0.2);
41
+ color: white;
42
+ }
43
+
44
+ .btn {
45
+ width: 100%;
46
+ padding: 0.75rem;
47
+ background: #9b87f5;
48
+ color: white;
49
+ border: none;
50
+ border-radius: 6px;
51
+ cursor: pointer;
52
+ font-size: 1rem;
53
+ transition: background 0.3s;
54
+ }
55
+
56
+ .btn:hover {
57
+ background: #7e69d6;
58
+ }
59
+
60
+ .links {
61
+ margin-top: 1rem;
62
+ text-align: center;
63
+ font-size: 0.9rem;
64
+ }
65
+
66
+ .links a {
67
+ color: #D6BCFA;
68
+ text-decoration: none;
69
+ }
70
+
71
+ .links a:hover {
72
+ text-decoration: underline;
73
+ }
74
+ </style>
75
+ </head>
76
+
77
+ <body class="dark">
78
+ <div class="auth-container">
79
+ <h2>Reset Password</h2>
80
+ <form id="resetPasswordForm">
81
+ <div class="form-group">
82
+ <label for="newPassword">New Password</label>
83
+ <input type="password" id="newPassword" required>
84
+ </div>
85
+ <div class="form-group">
86
+ <label for="confirmPassword">Confirm Password</label>
87
+ <input type="password" id="confirmPassword" required>
88
+ </div>
89
+ <button type="submit" class="btn">Reset Password</button>
90
+ </form>
91
+ <div class="links">
92
+ <a href="/login">Back to Login</a>
93
+ </div>
94
+ </div>
95
+
96
+ <script>
97
+ const urlParams = new URLSearchParams(window.location.search);
98
+ const token = urlParams.get('token');
99
+
100
+ if (!token) {
101
+ alert('Invalid reset link');
102
+ window.location.href = '/login';
103
+ }
104
+
105
+ document.getElementById('resetPasswordForm').addEventListener('submit', async (e) => {
106
+ e.preventDefault();
107
+ const newPassword = document.getElementById('newPassword').value;
108
+ const confirmPassword = document.getElementById('confirmPassword').value;
109
+
110
+ if (newPassword !== confirmPassword) {
111
+ alert('Passwords do not match');
112
+ return;
113
+ }
114
+
115
+ try {
116
+ const response = await fetch('/api/reset-password', {
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ token, new_password: newPassword })
120
+ });
121
+
122
+ const data = await response.json();
123
+ if (response.ok) {
124
+ alert('Password reset successful! Please login with your new password.');
125
+ window.location.href = '/login';
126
+ } else {
127
+ alert(data.detail || 'Password reset failed');
128
+ }
129
+ } catch (error) {
130
+ console.error('Error:', error);
131
+ alert('An error occurred');
132
+ }
133
+ });
134
+ </script>
135
+ </body>
136
+
137
+ </html>
src/apps/templates/resources.html ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- Add this section below the Safety Section in your existing code -->
2
+ <section class="safety-tips" id="safety">
3
+ <div class="grid-container">
4
+ <div class="feature-card">
5
+ <h3>🧠 Mental Health</h3>
6
+ <ul>
7
+ <li><i class="fas fa-phone-alt"></i> NIMHANS helpline: 080-4611 0007</li>
8
+
9
+ <!-- Yoga & Meditation Section -->
10
+ <li class="nested-item">
11
+ <div class="expandable-section">
12
+ <i class="fas fa-spa"></i> Yoga & Meditation Centers in Chennai
13
+ <i class="fas fa-chevron-down toggle-icon"></i>
14
+ </div>
15
+ <ul class="nested-list">
16
+ <li class="nested-list" onclick="window.open('https://www.ishayoga.org', '_blank')">
17
+ <strong>Isha Yoga Center</strong><br>
18
+ <small>Velliangiri Foothills | +91 83000 83111</small>
19
+ </li>
20
+ <li class="nested-list" onclick="window.open('https://aurosociety.org/', '_blank')">
21
+ <strong>Sri Aurobindo Society</strong><br>
22
+ <small>Alwarpet | +91 44 2499 6284</small>
23
+ </li>
24
+ <li class="nested-list" onclick="window.open('https://www.artofliving.org', '_blank')">
25
+ <strong>Art of Living Center</strong><br>
26
+ <small>Teynampet | +91 96000 96000</small>
27
+ </li>
28
+ <li class="nested-list" onclick="window.open('https://www.sivananda.org.in', '_blank')">
29
+ <strong>Sivananda Yoga Vedanta</strong><br>
30
+ <small>Mylapore | +91 44 2491 7726</small>
31
+ </li>
32
+
33
+ </ul>
34
+ </li>
35
+
36
+ <!-- Additional mental health resources can go here -->
37
+
38
+ </ul>
39
+ </div>
40
+
41
+ <div class="feature-card">
42
+ <h3>⚖️ Legal Remedies</h3>
43
+ <ul>
44
+ <li><i class="fas fa-clipboard-check"></i> Right to Constitutional Remedies (Article 32)</li>
45
+ <li><i class="fas fa-user-shield"></i> Right to Legal Aid</li>
46
+ <li><i class="fas fa-scale-balanced"></i> File Public Interest Litigation (PIL)</li>
47
+ <li><i class="fas fa-landmark"></i> Seek Protection Under Special Laws</li>
48
+ </ul>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Emergency Legal Assistance Section -->
53
+ <div class="law-section">
54
+ <h2>Immediate Health Assistance</h2>
55
+ <div class="quick-links">
56
+ <div class="safety-kit">
57
+ <p>✅ Dial 108 for ambulance services</p>
58
+ <p>✅ Telemedicine - eSanjeevani: World's largest telemedicine service</p>
59
+ <p>✅ Approach Lok Adalats for quick settlements</p>
60
+ <p>✅ Jan Aushadhi generic medicine stores locato</p>
61
+ </div>
62
+ <button class="safety-button" onclick="downloadLegalGuide()">
63
+ <i class="fas fa-download"></i>
64
+ Download Legal Rights Guide
65
+ </button>
66
+ </div>
67
+ </div>
68
+
69
+ <button class="back-button" onclick="goBack()">
70
+ <i class="fas fa-arrow-left"></i>
71
+ Back to Previous Page
72
+ </button>
73
+ </section>
74
+
75
+ <!-- Existing CSS for Safety Section -->
76
+ <style>
77
+ .nested-list {
78
+ cursor: pointer;
79
+ }
80
+ .safety-tips {
81
+ position: relative;
82
+ padding: 8rem 5%;
83
+ background: #f9f9f9;
84
+ }
85
+
86
+ .safety-tips .grid-container {
87
+ display: grid;
88
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
89
+ gap: 3rem;
90
+ }
91
+
92
+ .safety-tips .feature-card {
93
+ background: white;
94
+ border: 1px solid #ddd;
95
+ padding: 2.5rem;
96
+ border-radius: 12px;
97
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
98
+ }
99
+
100
+ .safety-tips .feature-card:hover {
101
+ transform: translateY(-5px);
102
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
103
+ }
104
+
105
+ .safety-tips h3 {
106
+ font-size: 1.5rem;
107
+ font-weight: 600;
108
+ margin-bottom: 1.5rem;
109
+ color: #333;
110
+ }
111
+
112
+ .safety-tips ul {
113
+ list-style: none;
114
+ padding: 0;
115
+ margin: 0;
116
+ }
117
+
118
+ .safety-tips li {
119
+ padding: 1rem 0;
120
+ border-bottom: 1px solid #eee;
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 1rem;
124
+ transition: transform 0.3s ease;
125
+ }
126
+
127
+ .safety-tips li:last-child {
128
+ border-bottom: none;
129
+ }
130
+
131
+ .safety-tips li:hover {
132
+ transform: translateX(8px);
133
+ }
134
+
135
+ .safety-tips i {
136
+ font-size: 1.1rem;
137
+ width: auto;
138
+ height: auto;
139
+ border-radius: 8px;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ background: #e91e63;
144
+ color: white;
145
+ flex-shrink: 0;
146
+ transition: all 0.3s ease;
147
+ }
148
+
149
+ .safety-kit {
150
+ background: white;
151
+ border-radius: 12px;
152
+ padding: 2.5rem;
153
+ border: 1px solid #ddd;
154
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
155
+ margin: 4rem 0;
156
+ }
157
+
158
+ .safety-button {
159
+ background: #e91e63;
160
+ border: none;
161
+ padding: 1rem 2rem;
162
+ border-radius: 8px;
163
+ color: white;
164
+ font-weight: 600;
165
+ cursor: pointer;
166
+ transition: all 0.3s ease;
167
+ display: flex;
168
+ }
169
+
170
+ .safety-button:hover {
171
+ background: #d81b60;
172
+ }
173
+
174
+ .back-button {
175
+ background: #555;
176
+ border: none;
177
+ padding: 1rem 2rem;
178
+ border-radius: 8px;
179
+ color: white;
180
+ font-weight: 600;
181
+ cursor: pointer;
182
+ transition: all 0.3s ease;
183
+ display: flex;
184
+ align-items: center;
185
+ margin-top: 2rem;
186
+ }
187
+
188
+ .back-button:hover {
189
+ background: #333;
190
+ }
191
+
192
+ @media (max-width: 768px) {
193
+ .safety-tips {
194
+ padding: 4rem 5%;
195
+ }
196
+ .safety-tips .feature-card {
197
+ padding: 1.75rem;
198
+ }
199
+ .safety-kit {
200
+ padding: 1.5rem;
201
+ }
202
+ }
203
+ </style>
204
+
205
+ <script>
206
+ function downloadLegalGuide() {
207
+ alert("Downloading Legal Rights Guide...");
208
+ }
209
+
210
+ function goBack() {
211
+ window.history.back();
212
+ }
213
+ </script>
src/apps/templates/roleselection.html ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Select Your Role</title>
8
+ <link rel="stylesheet" href="/static/css/styles.css">
9
+ <style>
10
+ .professional-footer {
11
+ margin-top: 2rem;
12
+ padding: 1rem 0;
13
+ text-align: center;
14
+ border-top: 1px solid rgba(155, 135, 245, 0.1);
15
+ color: rgb(19 18 18 / 50%);
16
+ font-size: 0.8rem;
17
+ width: 100%;
18
+ }
19
+
20
+ .professional-footer a {
21
+ color: #9b87f5;
22
+ text-decoration: none;
23
+ font-weight: 500;
24
+ }
25
+ </style>
26
+ </head>
27
+
28
+ <body>
29
+ <div class="overlay">
30
+ <div class="role-selection">
31
+ <h2>Who are you?</h2>
32
+ <p>Please select your role to continue:</p>
33
+ <div class="roles">
34
+ <button class="role" data-role="Judge">👨⚖️ Judge</button>
35
+ <button class="role" data-role="Advocate/Lawyer">👩⚖️ Advocate</button>
36
+ <button class="role" data-role="Woman">🛡️ Woman (Special Provisions)</button>
37
+ <button class="role" data-role="Citizen">👤 Citizen</button>
38
+ <button class="role" data-role="Student">🎓 Student</button>
39
+ <button class="role" data-role="Minor">👦 Minor</button>
40
+ </div>
41
+ <footer class="professional-footer">
42
+ &copy; 2026 Law Bot AI | Managed by <a href="https://www.linkedin.com/in/vishwanath77"
43
+ target="_blank">Vishwanath</a>
44
+ </footer>
45
+ </div>
46
+ </div>
47
+
48
+ <script src="/static/js/roleselection.js?v=3"></script>
49
+ </body>
50
+
51
+ </html>
src/apps/templates/safetytips.html ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- Add this section below the Safety Section in your existing code -->
2
+ <section class="safety-tips" id="safety">
3
+ <div class="grid-container">
4
+ <div class="feature-card">
5
+ <h3>🛡️ General Safety Tips</h3>
6
+ <ul>
7
+ <li><i class="fas fa-map-marker-alt"></i> Always share live location with trusted contacts</li>
8
+ <li><i class="fas fa-phone-volume"></i> Keep emergency numbers on speed dial (112, 1091)</li>
9
+ <li><i class="fas fa-car"></i> Verify cab details before boarding (Check number plate)</li>
10
+ <li><i class="fas fa-lock"></i> Avoid isolated ATMs/restrooms at night</li>
11
+ </ul>
12
+ </div>
13
+
14
+ <div class="feature-card">
15
+ <h3>🚆 Public Transport Safety</h3>
16
+ <ul>
17
+ <li><i class="fas fa-subway"></i> Use women-only coaches in metros/trains</li>
18
+ <li><i class="fas fa-bus"></i> Prefer front seats in buses</li>
19
+ <li><i class="fas fa-phone"></i> Download RailMadad app (152 for railway emergencies)</li>
20
+ <li><i class="fas fa-road"></i> Avoid empty bus stops at night</li>
21
+ </ul>
22
+ </div>
23
+
24
+ <div class="feature-card">
25
+ <h3>🏢 Workplace Safety</h3>
26
+ <ul>
27
+ <li><i class="fas fa-gavel"></i> Know POSH Act 2013 provisions</li>
28
+ <li><i class="fas fa-file-alt"></i> Document all incidents formally</li>
29
+ <li><i class="fas fa-users"></i> Use company transport for late hours</li>
30
+ <li><i class="fas fa-exclamation-triangle"></i> Report inappropriate behavior immediately</li>
31
+ </ul>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Emergency Preparedness Section -->
36
+ <div class="law-section">
37
+ <h2>Emergency Preparedness Kit</h2>
38
+ <div class="quick-links">
39
+ <div class="safety-kit">
40
+ <p>✅ Keep pepper spray in easy-to-reach pocket</p>
41
+ <p>✅ Save ICE (In Case of Emergency) contacts. (Settings > Safety & Emergency > Emergency Contacts)
42
+ <p>✅ Create code word with family for danger situations</p>
43
+ <p>✅ Keep ₹100 cash separate for emergencies</p>
44
+ </div>
45
+ <button class="safety-button" onclick="downloadSafetyGuide()">
46
+ <i class="fas fa-download"></i>
47
+ Download Safety Guide
48
+ </button>
49
+ </div>
50
+ </div>
51
+ <button class="back-button" onclick="goBack()">
52
+ <i class="fas fa-arrow-left"></i>
53
+ Back to Previous Page
54
+ </button>
55
+ </section>
56
+
57
+ <!-- CSS for Safety Section -->
58
+ <style>
59
+ .safety-tips {
60
+ position: relative;
61
+ padding: 8rem 5%;
62
+ background: #f9f9f9;
63
+ }
64
+
65
+ .safety-tips .grid-container {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
68
+ gap: 3rem;
69
+ }
70
+
71
+ .safety-tips .feature-card {
72
+ background: white;
73
+ border: 1px solid #ddd;
74
+ padding: 2.5rem;
75
+ border-radius: 12px;
76
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
77
+ }
78
+
79
+ .safety-tips .feature-card:hover {
80
+ transform: translateY(-5px);
81
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
82
+ }
83
+
84
+ .safety-tips h3 {
85
+ font-size: 1.5rem;
86
+ font-weight: 600;
87
+ margin-bottom: 1.5rem;
88
+ color: #333;
89
+ }
90
+
91
+ .safety-tips ul {
92
+ list-style: none;
93
+ padding: 0;
94
+ margin: 0;
95
+ }
96
+
97
+ .safety-tips li {
98
+ padding: 1rem 0;
99
+ border-bottom: 1px solid #eee;
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 1rem;
103
+ transition: transform 0.3s ease;
104
+ }
105
+
106
+ .safety-tips li:last-child {
107
+ border-bottom: none;
108
+ }
109
+
110
+ .safety-tips li:hover {
111
+ transform: translateX(8px);
112
+ }
113
+
114
+ .safety-tips i {
115
+ font-size: 1.1rem;
116
+ width: auto;
117
+ height: auto;
118
+ border-radius: 8px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ background: #e91e63;
123
+ color: white;
124
+ flex-shrink: 0;
125
+ transition: all 0.3s ease;
126
+ }
127
+
128
+ .safety-kit {
129
+ background: white;
130
+ border-radius: 12px;
131
+ padding: 2.5rem;
132
+ border: 1px solid #ddd;
133
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
134
+ margin: 4rem 0;
135
+ }
136
+
137
+ .safety-button {
138
+ background: #e91e63;
139
+ border: none;
140
+ padding: 1rem 2rem;
141
+ border-radius: 8px;
142
+ color: white;
143
+ font-weight: 600;
144
+ cursor: pointer;
145
+ transition: all 0.3s ease;
146
+ display: flex;
147
+ }
148
+
149
+ .safety-button:hover {
150
+ background: #d81b60;
151
+ }
152
+ .back-button {
153
+ background: #555;
154
+ border: none;
155
+ padding: 1rem 2rem;
156
+ border-radius: 8px;
157
+ color: white;
158
+ font-weight: 600;
159
+ cursor: pointer;
160
+ transition: all 0.3s ease;
161
+ display: flex;
162
+ align-items: center;
163
+ margin-top: 2rem;
164
+ }
165
+
166
+ @media (max-width: 768px) {
167
+ .safety-tips {
168
+ padding: 4rem 5%;
169
+ }
170
+ .safety-tips .feature-card {
171
+ padding: 1.75rem;
172
+ }
173
+ .safety-kit {
174
+ padding: 1.5rem;
175
+ }
176
+ }
177
+ </style>
178
+
179
+ <script>
180
+ function downloadSafetyGuide() {
181
+ alert("Downloading Women's Safety Handbook...");
182
+ }
183
+ function goBack() {
184
+ window.history.back();
185
+ }
186
+ </script>
src/apps/templates/script.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const form = document.getElementById("registerForm");
3
+ if (!form) return;
4
+
5
+ form.addEventListener("submit", handleSubmit);
6
+
7
+ async function handleSubmit(e) {
8
+ e.preventDefault();
9
+
10
+ const getValue = id => document.getElementById(id)?.value.trim();
11
+
12
+ const user = {
13
+ name: getValue("name"),
14
+ email: getValue("email"),
15
+ password: getValue("password")
16
+ };
17
+
18
+ if (!user.name || !user.email || !user.password) {
19
+ alert("Please fill all fields");
20
+ return;
21
+ }
22
+
23
+ try {
24
+ const response = await fetch("/api/register", {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify(user)
28
+ });
29
+
30
+ const data = await response.json();
31
+
32
+ if (!response.ok) throw new Error(data.message || "Registration failed");
33
+
34
+ alert("Success! Redirecting...");
35
+ setTimeout(() => window.location.href = "login.html", 500);
36
+
37
+ } catch (error) {
38
+ console.error("Error:", error);
39
+ alert(error.message);
40
+ }
41
+ }
42
+ });
src/apps/templates/studentchatbot.html ADDED
@@ -0,0 +1,855 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Law bot</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="/static/css/modern-chat.css">
10
+ <style>
11
+ :root {
12
+ --primary-purple: #9b87f5;
13
+ --dark-purple: #1A1F2C;
14
+ --light-purple: #D6BCFA;
15
+ --neutral-gray: #8E9196;
16
+ --light-gray: #C8C8C9;
17
+ --off-white: #eee;
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ font-family: 'Inter', sans-serif;
25
+ }
26
+
27
+ body {
28
+ transition: background-color 0.3s, color 0.3s;
29
+ }
30
+
31
+ body.dark {
32
+ color: var(--off-white);
33
+ }
34
+
35
+ body.light {
36
+ color: var(--dark-purple);
37
+ }
38
+
39
+ .container {
40
+ max-width: 800px;
41
+ margin: 0 auto;
42
+ padding: 2rem;
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ .header {
49
+ display: flex;
50
+ justify-content: space-between;
51
+ align-items: center;
52
+ margin-bottom: 2rem;
53
+ }
54
+
55
+ .header-right {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 1rem;
59
+ }
60
+
61
+
62
+ .app-title {
63
+ margin-left: 40px;
64
+ font-size: 2rem;
65
+ font-weight: 600;
66
+ background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
+ -webkit-background-clip: text;
68
+ background-clip: text;
69
+ color: transparent;
70
+ }
71
+
72
+ .theme-toggle {
73
+ background: none;
74
+ border: none;
75
+ cursor: pointer;
76
+ padding: 0.5rem;
77
+ color: var(--primary-purple);
78
+ transition: opacity 0.3s;
79
+ }
80
+
81
+ .theme-toggle:hover {
82
+ opacity: 0.8;
83
+ }
84
+
85
+ .theme-toggle svg {
86
+ width: 24px;
87
+ height: 24px;
88
+ }
89
+
90
+ .chat-container {
91
+ flex-grow: 1;
92
+ overflow-y: auto;
93
+ margin-bottom: 2rem;
94
+ padding: 1rem;
95
+ }
96
+
97
+ .message {
98
+ margin-bottom: 1.5rem;
99
+ padding: 1rem;
100
+ border-radius: 8px;
101
+ animation: fadeIn 0.3s ease-in;
102
+ }
103
+
104
+ .user-message {
105
+ background-color: rgba(155, 135, 245, 0.1);
106
+ }
107
+
108
+ .ai-message {
109
+ background-color: rgba(255, 255, 255, 0.05);
110
+ border: 1px solid rgba(200, 200, 201, 0.2);
111
+ }
112
+
113
+ body.light .user-message {
114
+ background-color: var(--off-white);
115
+ }
116
+
117
+ body.light .ai-message {
118
+ background-color: #ffffff;
119
+ border: 1px solid var(--light-gray);
120
+ }
121
+
122
+ .message-header {
123
+ display: flex;
124
+ align-items: center;
125
+ margin-bottom: 0.5rem;
126
+ font-weight: 500;
127
+ }
128
+
129
+
130
+ .sidebar-welcome {
131
+ padding: 1.5rem;
132
+ border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
+ margin-bottom: 1.5rem;
134
+ }
135
+
136
+ .user-icon,
137
+ .ai-icon {
138
+ width: 24px;
139
+ height: 24px;
140
+ border-radius: 50%;
141
+ margin-right: 0.5rem;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ font-size: 12px;
146
+ }
147
+
148
+ .user-icon {
149
+ background-color: var(--primary-purple);
150
+ color: white;
151
+ }
152
+
153
+ .ai-icon {
154
+ background-color: var(--light-purple);
155
+ color: var(--dark-purple);
156
+ }
157
+
158
+ .input-container {
159
+ position: fixed;
160
+ bottom: 0;
161
+ left: 0;
162
+ right: 0;
163
+ padding: 1rem;
164
+ background-color: var(--input-bg);
165
+ border-top: 1px solid var(--sidebar-border);
166
+ }
167
+
168
+ body.light .input-container {
169
+ background-color: #ffffff;
170
+ border-top: 1px solid var(--light-gray);
171
+ }
172
+
173
+ .input-wrapper {
174
+ max-width: 800px;
175
+ margin: 0 auto;
176
+ position: relative;
177
+ }
178
+
179
+ #messageInput {
180
+ width: 100%;
181
+ padding: 1rem;
182
+ padding-right: 3rem;
183
+ border: 1px solid rgba(200, 200, 201, 0.3);
184
+ border-radius: 8px;
185
+ font-size: 1rem;
186
+ outline: none;
187
+ transition: border-color 0.3s;
188
+ background-color: transparent;
189
+ color: inherit;
190
+ }
191
+
192
+ body.light #messageInput {
193
+ border: 1px solid var(--light-gray);
194
+ }
195
+
196
+ #messageInput:focus {
197
+ border-color: var(--primary-purple);
198
+ }
199
+
200
+ #sendButton {
201
+ position: absolute;
202
+ right: 0.5rem;
203
+ top: 50%;
204
+ transform: translateY(-50%);
205
+ background: none;
206
+ border: none;
207
+ cursor: pointer;
208
+ padding: 0.5rem;
209
+ color: var(--primary-purple);
210
+ opacity: 0.8;
211
+ transition: opacity 0.3s;
212
+ }
213
+
214
+ #sendButton:hover {
215
+ opacity: 1;
216
+ }
217
+
218
+ .typing-indicator {
219
+ padding: 1rem;
220
+ color: var(--neutral-gray);
221
+ font-style: italic;
222
+ display: none;
223
+ }
224
+
225
+ .welcome-title {
226
+ margin: 6rem 0 0.5rem 0;
227
+ text-align: center;
228
+ color: var(--neutral-gray);
229
+ padding: 1rem;
230
+ font-size: 1.24rem;
231
+ animation: fadeIn 0.3s ease-in;
232
+ }
233
+
234
+ .welcome-subtitle {
235
+ line-height: 1.6;
236
+ font-size: 1.1rem;
237
+ color: var(--neutral-gray);
238
+ max-width: 800px;
239
+ margin: 0 auto;
240
+ font-weight: 400;
241
+ color: var(--primary-purple);
242
+ }
243
+
244
+ .sidebar-welcome {
245
+ margin-bottom: 2rem;
246
+ padding: 0 1rem;
247
+ border-bottom: none;
248
+ }
249
+
250
+ .role-selection-link {
251
+ display: inline-flex;
252
+ align-items: center;
253
+ gap: initial;
254
+ color: var(--light-purple);
255
+ text-decoration: none;
256
+ margin-top: auto;
257
+ padding: auto;
258
+ transition: opacity 0.3s ease;
259
+ position: absolute;
260
+ top: 550px;
261
+ left: 30px;
262
+
263
+ }
264
+
265
+ .role-selection-link:hover {
266
+ opacity: 0.8;
267
+ }
268
+
269
+ .role-selection-link svg {
270
+ width: 16px;
271
+ height: 16px;
272
+ stroke: currentColor;
273
+ }
274
+
275
+ /* Add this media query for mobile responsiveness */
276
+ @media (max-width: 480px) {
277
+ .welcome-message p {
278
+ font-size: 1rem;
279
+ padding: 0 1rem;
280
+ }
281
+ }
282
+
283
+ @keyframes fadeIn {
284
+ from {
285
+ opacity: 0;
286
+ transform: translateY(10px);
287
+ }
288
+
289
+ to {
290
+ opacity: 1;
291
+ transform: translateY(0);
292
+ }
293
+ }
294
+
295
+ .connection-status {
296
+ position: fixed;
297
+ top: 1rem;
298
+ right: 1rem;
299
+ padding: 0.5rem 1rem;
300
+ border-radius: 4px;
301
+ font-size: 0.875rem;
302
+ display: none;
303
+ }
304
+
305
+ .connection-status.connected {
306
+ background-color: #4ade80;
307
+ color: white;
308
+ }
309
+
310
+ .connection-status.disconnected {
311
+ background-color: #ef4444;
312
+ color: white;
313
+ }
314
+
315
+ /* Styling for formatted text */
316
+ .message-content {
317
+ line-height: 1.5;
318
+ }
319
+
320
+ .message-content h1,
321
+ .message-content h2,
322
+ .message-content h3 {
323
+ margin: 1rem 0 0.5rem;
324
+ font-weight: 600;
325
+ }
326
+
327
+ .message-content h1 {
328
+ font-size: 1.5rem;
329
+ }
330
+
331
+ .message-content h2 {
332
+ font-size: 1.25rem;
333
+ }
334
+
335
+ .message-content h3 {
336
+ font-size: 1.1rem;
337
+ }
338
+
339
+ .message-content ul,
340
+ .message-content ol {
341
+ margin-left: 1.5rem;
342
+ margin-bottom: 1rem;
343
+ }
344
+
345
+ .message-content a {
346
+ color: var(--primary-purple);
347
+ text-decoration: none;
348
+ }
349
+
350
+ .message-content a:hover {
351
+ text-decoration: underline;
352
+ }
353
+
354
+ .usage-indicator {
355
+ padding: 4px 12px;
356
+ background: rgba(155, 135, 245, 0.1);
357
+ border: 1px solid rgba(155, 135, 245, 0.3);
358
+ border-radius: 20px;
359
+ font-size: 0.8rem;
360
+ color: var(--primary-purple);
361
+ font-weight: 500;
362
+ display: none;
363
+ /* Hidden by default for Admins */
364
+ }
365
+ </style>
366
+ </head>
367
+
368
+ <button class="sidebar-floating-toggle" id="floatingToggle">
369
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
370
+ <path d="M3 12h18M3 6h18M3 18h18" />
371
+ </svg>
372
+ </button>
373
+
374
+ <aside class="sidebar" id="sidebar">
375
+ <div class="sidebar-header">
376
+ <button class="new-chat-btn" id="newChatBtn">
377
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378
+ <path d="M12 5v14M5 12h14" />
379
+ </svg>
380
+ New Chat
381
+ </button>
382
+ <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
383
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
384
+ <path d="M15 18l-6-6 6-6" />
385
+ </svg>
386
+ </button>
387
+ </div>
388
+ <div class="sidebar-title">Recent Research</div>
389
+ <div class="history-list" id="historyList">
390
+ <!-- Recent prompts will appear here -->
391
+ </div>
392
+
393
+ <div class="perspective-container" style="display: none;">
394
+ <div class="sidebar-title">Answer Perspective</div>
395
+ <div class="perspective-list">
396
+ <div class="perspective-option active" data-role="Student">🎓 Student</div>
397
+ </div>
398
+ </div>
399
+
400
+
401
+ </aside>
402
+
403
+ <!-- Existing messages will be added here dynamically -->
404
+ <div class="container">
405
+ <header class="header">
406
+ <h1 class="app-title">Law Bot (Student)</h1>
407
+ <div class="header-right">
408
+ <div class="usage-indicator" id="usageIndicator">
409
+ Questions Remaining: <span id="remainingCount">--</span>
410
+ </div>
411
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
412
+ <!-- Icon injected by JS -->
413
+ </button>
414
+
415
+ <a href="/studentdashboard.html" class="dashboard-link" title="Back to Dashboard">
416
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
417
+ <rect x="3" y="3" width="7" height="7"></rect>
418
+ <rect x="14" y="3" width="7" height="7"></rect>
419
+ <rect x="14" y="14" width="7" height="7"></rect>
420
+ <rect x="3" y="14" width="7" height="7"></rect>
421
+ </svg>
422
+ </a>
423
+ </div>
424
+ </header>
425
+ <div class="welcome-title">Welcome, Future Advocate! 👨‍🎓</div>
426
+ <div class="welcome-subtitle">Ready to sharpen your legal reasoning? How can I assist your preparations today?
427
+ </div>
428
+
429
+
430
+ <div class="chat-container" id="chatContainer">
431
+ <div class="typing-indicator" id="typingIndicator">
432
+ <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--student-color)" stroke-width="2">
433
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
434
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
435
+ </svg>
436
+ <span>Researching for your studies...</span>
437
+ </div>
438
+ </div>
439
+ <div class="input-container">
440
+ <div class="input-wrapper">
441
+ <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
442
+ <button id="sendButton">
443
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
444
+ stroke-linecap="round" stroke-linejoin="round">
445
+ <line x1="22" y1="2" x2="11" y2="13"></line>
446
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
447
+ </svg>
448
+ </button>
449
+ </div>
450
+ </div>
451
+ <footer class="professional-footer">
452
+ &copy; 2026 Law Bot AI. All Rights Reserved.
453
+ <br>
454
+ Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
455
+ </footer>
456
+ </div>
457
+ <div class="connection-status" id="connectionStatus"></div>
458
+
459
+ <script>
460
+ let ws;
461
+ const chatContainer = document.getElementById('chatContainer');
462
+ const messageInput = document.getElementById('messageInput');
463
+ const sendButton = document.getElementById('sendButton');
464
+ const typingIndicator = document.getElementById('typingIndicator');
465
+ const connectionStatus = document.getElementById('connectionStatus');
466
+ const sidebar = document.getElementById('sidebar');
467
+
468
+ let currentUserMessage = '';
469
+ let currentAiMessage = '';
470
+ let currentAiMessageElement = null;
471
+ let currentCaseId = crypto.randomUUID();
472
+ const activeRole = 'Student'; // LOCKED ROLE
473
+ let userLimitReached = false;
474
+
475
+ async function checkUserStatus() {
476
+ const token = localStorage.getItem('token');
477
+ if (!token) return;
478
+
479
+ try {
480
+ const response = await fetch('/api/user-status', {
481
+ headers: { 'Authorization': `Bearer ${token}` }
482
+ });
483
+ const status = await response.json();
484
+
485
+ const indicator = document.getElementById('usageIndicator');
486
+ if (status.is_admin) {
487
+ indicator.style.display = 'none';
488
+ } else {
489
+ indicator.style.display = 'block';
490
+ const remaining = Math.max(0, 2 - status.question_count);
491
+ document.getElementById('remainingCount').innerText = remaining;
492
+ if (remaining <= 0) {
493
+ userLimitReached = true;
494
+ }
495
+ }
496
+ } catch (err) {
497
+ console.error('Failed to fetch user status:', err);
498
+ }
499
+ }
500
+
501
+ function formatText(text) {
502
+ // Extract references for Evidence Box
503
+ const references = [];
504
+ const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
505
+ let match;
506
+ while ((match = refRegex.exec(text)) !== null) {
507
+ references.push({ title: match[1], pages: match[2] });
508
+ }
509
+
510
+ let formatted = text
511
+ .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
512
+ .replace(/\*(.*?)\*/g, "<em>$1</em>")
513
+ .replace(/### (.*?)\n/g, "<h3>$1</h3>")
514
+ .replace(/## (.*?)\n/g, "<h2>$1</h2>")
515
+ .replace(/# (.*?)\n/g, "<h1>$1</h1>")
516
+ .replace(/\n/g, "<br>")
517
+ .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
518
+ .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
519
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
520
+
521
+ // Add Evidence Box if references found
522
+ if (references.length > 0) {
523
+ let evidenceHtml = `<div class="evidence-box">
524
+ <div class="evidence-title">
525
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
526
+ Study Evidence
527
+ </div>`;
528
+ references.forEach(ref => {
529
+ evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
530
+ });
531
+ evidenceHtml += `</div>`;
532
+ formatted += evidenceHtml;
533
+ }
534
+ return formatted;
535
+ }
536
+
537
+ function connectWebSocket() {
538
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
539
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
540
+
541
+ ws.onopen = () => {
542
+ console.log('Connected to WebSocket');
543
+ connectionStatus.textContent = 'Connected';
544
+ connectionStatus.className = 'connection-status connected';
545
+ connectionStatus.style.display = 'block';
546
+ setTimeout(() => {
547
+ connectionStatus.style.display = 'none';
548
+ }, 3000);
549
+ };
550
+
551
+ ws.onclose = () => {
552
+ console.log('Disconnected from WebSocket');
553
+ connectionStatus.textContent = 'Reconnecting...';
554
+ connectionStatus.className = 'connection-status disconnected';
555
+ connectionStatus.style.display = 'block';
556
+
557
+ // Exponential backoff for reconnection
558
+ const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
559
+ window._reconnectCount = (window._reconnectCount || 0) + 1;
560
+
561
+ setTimeout(() => {
562
+ reconnectWebSocket();
563
+ loadHistory();
564
+ }, backoff);
565
+ };
566
+
567
+ ws.onerror = (error) => {
568
+ console.error('WebSocket Error:', error);
569
+ };
570
+
571
+
572
+ ws.onmessage = (event) => {
573
+ console.log('Received message:', event.data);
574
+
575
+ // ✅ Hide typing indicator on response
576
+ typingIndicator.style.display = 'none';
577
+
578
+ if (event.data === '[DONE]') {
579
+ // Save complete chat interaction
580
+ saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
581
+ checkUserStatus(); // Update remaining count
582
+ return;
583
+ }
584
+
585
+ // Handle usage limit blocks from backend
586
+ if (event.data.includes("Free usage limit reached")) {
587
+ typingIndicator.style.display = 'none';
588
+ userLimitReached = true;
589
+ checkUserStatus();
590
+ }
591
+
592
+ if (!currentAiMessageElement) {
593
+ currentAiMessage = event.data;
594
+ addMessage(currentAiMessage, 'ai');
595
+ } else {
596
+ currentAiMessage += event.data;
597
+ const formattedMessage = formatText(currentAiMessage);
598
+ currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
599
+ }
600
+ };
601
+
602
+ // ✅ Scroll fix (move into sendMessage function if needed)
603
+ chatContainer.scrollTop = chatContainer.scrollHeight;
604
+ }
605
+
606
+ function reconnectWebSocket() {
607
+ console.log('Attempting to reconnect...');
608
+ connectWebSocket();
609
+ loadHistory();
610
+ }
611
+
612
+ function addMessage(content, type) {
613
+ if (type === 'user') {
614
+ currentUserMessage = content;
615
+ currentAiMessage = '';
616
+ currentAiMessageElement = null;
617
+ }
618
+
619
+ const messageDiv = document.createElement('div');
620
+ messageDiv.className = `message ${type}-message`;
621
+
622
+ const header = document.createElement('div');
623
+ header.className = 'message-header';
624
+
625
+ const icon = document.createElement('div');
626
+ icon.className = `${type}-icon`;
627
+ icon.textContent = type === 'user' ? 'U' : 'L';
628
+
629
+ const name = document.createElement('span');
630
+ name.textContent = type === 'user' ? 'You' : 'Law Bot';
631
+
632
+ header.appendChild(icon);
633
+ header.appendChild(name);
634
+
635
+ const text = document.createElement('div');
636
+ text.className = 'message-content';
637
+ text.innerHTML = type === 'ai' ? formatText(content) : content;
638
+
639
+ messageDiv.appendChild(header);
640
+ messageDiv.appendChild(text);
641
+ chatContainer.appendChild(messageDiv);
642
+ chatContainer.scrollTop = chatContainer.scrollHeight;
643
+
644
+ if (type === 'ai') {
645
+ currentAiMessageElement = messageDiv;
646
+ }
647
+ }
648
+
649
+ function sendMessage() {
650
+ const message = messageInput.value.trim();
651
+ if (!message) return;
652
+
653
+ if (userLimitReached) {
654
+ addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
655
+ return;
656
+ }
657
+
658
+ if (!activeRole) {
659
+ addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
660
+ return;
661
+ }
662
+
663
+ if (ws.readyState === WebSocket.OPEN) {
664
+ addMessage(message, 'user');
665
+ ws.send(message);
666
+ messageInput.value = '';
667
+
668
+ // ✅ Show typing indicator after sending
669
+ typingIndicator.style.display = 'block';
670
+ chatContainer.scrollTop = chatContainer.scrollHeight;
671
+ }
672
+ }
673
+ const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
674
+ if (isAtBottom) {
675
+ chatContainer.scrollTop = chatContainer.scrollHeight;
676
+ }
677
+
678
+
679
+ const historyList = document.getElementById('historyList');
680
+
681
+ async function loadHistory() {
682
+ const token = localStorage.getItem('token');
683
+ if (!token) return;
684
+
685
+ try {
686
+ const response = await fetch(`/api/interactions?role=${activeRole}`, {
687
+ headers: { 'Authorization': `Bearer ${token}` }
688
+ });
689
+ const interactions = await response.json();
690
+ historyList.innerHTML = '';
691
+ interactions.forEach(item => {
692
+ const div = document.createElement('div');
693
+ div.className = 'history-item';
694
+
695
+ // Readable preview: first sentence or truncated query
696
+ const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
697
+
698
+ div.innerHTML = `
699
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
700
+ <span>${preview}</span>
701
+ <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
702
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
703
+ </button>
704
+ `;
705
+ div.onclick = () => loadConversation(item.case_id);
706
+ historyList.appendChild(div);
707
+ });
708
+ } catch (err) {
709
+ console.error('Failed to load history:', err);
710
+ }
711
+ }
712
+
713
+ async function loadConversation(caseId) {
714
+ const token = localStorage.getItem('token');
715
+ try {
716
+ const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
717
+ headers: { 'Authorization': `Bearer ${token}` }
718
+ });
719
+ const thread = await response.json();
720
+
721
+ currentCaseId = caseId;
722
+ chatContainer.innerHTML = ''; // Clear window
723
+
724
+ thread.forEach(msg => {
725
+ addMessage(msg.query, 'user');
726
+ // Render AI Response immediately
727
+ const aiMsgDiv = document.createElement('div');
728
+ aiMsgDiv.className = 'message ai-message';
729
+ aiMsgDiv.innerHTML = `
730
+ <div class="message-header">
731
+ <div class="ai-icon">L</div>
732
+ <span>Law Bot</span>
733
+ </div>
734
+ <div class="message-content">${formatText(msg.response)}</div>
735
+ `;
736
+ chatContainer.appendChild(aiMsgDiv);
737
+ });
738
+ chatContainer.scrollTop = chatContainer.scrollHeight;
739
+ } catch (err) {
740
+ console.error('Failed to load thread:', err);
741
+ }
742
+ }
743
+
744
+ async function deleteConversation(caseId) {
745
+ const token = localStorage.getItem('token');
746
+ try {
747
+ await fetch(`/api/interactions/${caseId}`, {
748
+ method: 'DELETE',
749
+ headers: { 'Authorization': `Bearer ${token}` }
750
+ });
751
+ if (currentCaseId === caseId) {
752
+ newChat();
753
+ } else {
754
+ loadHistory();
755
+ }
756
+ } catch (err) {
757
+ console.error('Failed to delete:', err);
758
+ }
759
+ }
760
+
761
+ function newChat() {
762
+ currentCaseId = crypto.randomUUID();
763
+ chatContainer.innerHTML = '';
764
+ messageInput.value = '';
765
+ messageInput.focus();
766
+ loadHistory();
767
+ }
768
+
769
+ // ✅ Modified: Uses Bearer token
770
+ const saveChatInteraction = async (caseId, query, response) => {
771
+ const token = localStorage.getItem('token');
772
+ if (!token) return;
773
+
774
+ try {
775
+ await fetch('/api/save-interaction', {
776
+ method: 'POST',
777
+ headers: {
778
+ 'Content-Type': 'application/json',
779
+ 'Authorization': `Bearer ${token}`
780
+ },
781
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
782
+ });
783
+ loadHistory(); // Refresh sidebar history
784
+ } catch (error) {
785
+ console.error('Error saving chat:', error);
786
+ }
787
+ };
788
+
789
+ sendButton.addEventListener('click', sendMessage);
790
+ messageInput.addEventListener('keypress', (e) => {
791
+ if (e.key === 'Enter') {
792
+ sendMessage();
793
+ }
794
+ });
795
+
796
+
797
+
798
+
799
+ // Theme toggle
800
+ const themeToggle = document.getElementById('themeToggle');
801
+ const body = document.body;
802
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
803
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
804
+
805
+ function updateTheme(isDark) {
806
+ if (isDark) {
807
+ body.classList.add('dark');
808
+ body.classList.remove('light');
809
+ themeToggle.innerHTML = moonIcon;
810
+ localStorage.setItem('theme', 'dark');
811
+ } else {
812
+ body.classList.add('light');
813
+ body.classList.remove('dark');
814
+ themeToggle.innerHTML = sunIcon;
815
+ localStorage.setItem('theme', 'light');
816
+ }
817
+ }
818
+
819
+ themeToggle.addEventListener('click', () => {
820
+ const isNowDark = !body.classList.contains('dark');
821
+ updateTheme(isNowDark);
822
+ });
823
+
824
+ // Initialize Theme
825
+ const savedTheme = localStorage.getItem('theme') || 'dark';
826
+ updateTheme(savedTheme === 'dark');
827
+
828
+ // Sidebar Collapse Logic
829
+ document.getElementById('collapseSidebar').onclick = () => {
830
+ document.body.classList.add('sidebar-collapsed');
831
+ };
832
+ document.getElementById('floatingToggle').onclick = () => {
833
+ document.body.classList.remove('sidebar-collapsed');
834
+ };
835
+ document.getElementById('newChatBtn').onclick = newChat;
836
+
837
+ // Perspective Selection
838
+ document.querySelectorAll('.perspective-option').forEach(opt => {
839
+ opt.onclick = () => {
840
+ document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
841
+ opt.classList.add('active');
842
+ activeRole = opt.dataset.role;
843
+ localStorage.setItem('activeRole', activeRole);
844
+ // No session reset needed, just role update for next message
845
+ };
846
+ });
847
+
848
+ checkUserStatus();
849
+ connectWebSocket();
850
+ loadHistory();
851
+
852
+ </script>
853
+ </body>
854
+
855
+ </html>
src/apps/templates/studentdashboard.html ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Law Student Dashboard</title>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <link rel="stylesheet" href="/static/css/entrance.css">
9
+ <script src="/static/js/entrance.js" defer></script>
10
+ <style>
11
+ /* Reset */
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: "Segoe UI", sans-serif;
17
+ }
18
+
19
+ body {
20
+ background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
21
+ min-height: 100vh;
22
+ display: flex;
23
+ }
24
+
25
+ /* Sidebar */
26
+ .sidebar {
27
+ width: 250px;
28
+ background: #0f172a;
29
+ color: white;
30
+ padding: 20px;
31
+ position: fixed;
32
+ height: 100vh;
33
+ overflow-y: auto;
34
+ transition: all 0.3s;
35
+ }
36
+
37
+ .sidebar h2 {
38
+ text-align: center;
39
+ margin-bottom: 30px;
40
+ font-size: 24px;
41
+ border-bottom: 2px solid #64748b;
42
+ padding-bottom: 10px;
43
+ }
44
+
45
+ .sidebar a {
46
+ display: block;
47
+ color: #e2e8f0;
48
+ padding: 12px 15px;
49
+ margin: 10px 0;
50
+ border-radius: 8px;
51
+ transition: 0.3s ease;
52
+ text-decoration: none;
53
+ }
54
+
55
+ .sidebar a:hover {
56
+ background: #1e293b;
57
+ }
58
+
59
+ /* Main Content */
60
+ .main {
61
+ margin-left: 270px;
62
+ padding: 30px;
63
+ flex: 1;
64
+ }
65
+
66
+ .header {
67
+ font-size: 28px;
68
+ font-weight: bold;
69
+ color: #1e293b;
70
+ margin-bottom: 25px;
71
+ }
72
+
73
+ .cards {
74
+ display: grid;
75
+ grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
76
+ gap: 20px;
77
+ }
78
+
79
+ .card {
80
+ background: white;
81
+ padding: 20px;
82
+ border-radius: 15px;
83
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
84
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
85
+ cursor: pointer;
86
+ position: relative;
87
+ overflow: hidden;
88
+ }
89
+
90
+ .card:hover {
91
+ transform: translateY(-10px);
92
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
93
+ }
94
+
95
+ .card::before {
96
+ content: '';
97
+ position: absolute;
98
+ width: 150%;
99
+ height: 150%;
100
+ background: radial-gradient(circle at center, rgba(59, 130, 246, 0.1), transparent);
101
+ top: -50%;
102
+ left: -50%;
103
+ transition: all 0.5s ease;
104
+ }
105
+
106
+ .card:hover::before {
107
+ top: -30%;
108
+ left: -30%;
109
+ }
110
+
111
+ .card h3 {
112
+ margin-bottom: 10px;
113
+ color: #0f172a;
114
+ }
115
+
116
+ .card p {
117
+ color: #475569;
118
+ }
119
+
120
+ /* Chatbot Button */
121
+ .chatbot-btn {
122
+ position: fixed;
123
+ bottom: 30px;
124
+ right: 30px;
125
+ background: #2563eb;
126
+ color: white;
127
+ border: none;
128
+ padding: 15px 20px;
129
+ border-radius: 50px;
130
+ font-size: 16px;
131
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
132
+ cursor: pointer;
133
+ transition: 0.3s ease;
134
+ }
135
+
136
+ .chatbot-btn:hover {
137
+ background: #1e40af;
138
+ }
139
+
140
+ @media(max-width: 768px) {
141
+ .sidebar {
142
+ width: 100%;
143
+ height: auto;
144
+ position: relative;
145
+ }
146
+
147
+ .main {
148
+ margin-left: 0;
149
+ }
150
+ }
151
+
152
+ #content-area {
153
+ margin-top: 40px;
154
+ padding: 25px;
155
+ background: #ffffff;
156
+ border-radius: 15px;
157
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
158
+ display: none;
159
+ }
160
+
161
+ #content-area h2 {
162
+ color: #0f172a;
163
+ margin-bottom: 15px;
164
+ }
165
+
166
+ #content-area ul {
167
+ list-style: disc;
168
+ padding-left: 20px;
169
+ }
170
+
171
+ #content-area li {
172
+ margin-bottom: 10px;
173
+ color: #334155;
174
+ }
175
+
176
+ #content-area a {
177
+ text-decoration: none;
178
+ color: #2563eb;
179
+ }
180
+
181
+ #content-area a:hover {
182
+ text-decoration: underline;
183
+ }
184
+
185
+ #content-area {
186
+ animation: fadeIn 0.3s ease-in-out;
187
+ }
188
+
189
+ @keyframes fadeIn {
190
+ from {
191
+ opacity: 0;
192
+ transform: translateY(10px);
193
+ }
194
+
195
+ to {
196
+ opacity: 1;
197
+ transform: translateY(0);
198
+ }
199
+ }
200
+
201
+ .back-video {
202
+ position: fixed;
203
+ right: 0;
204
+ bottom: 0;
205
+ min-height: 100%;
206
+ min-width: 100%;
207
+ z-index: -1;
208
+ }
209
+
210
+ @media (min-aspect-ratio:16/9) {
211
+ .back-video {
212
+ width: auto;
213
+ height: auto;
214
+ }
215
+ }
216
+
217
+ @media (max-aspect-ratio:16/9) {
218
+ .back-video {
219
+ width: auto;
220
+ height: 100%;
221
+ }
222
+ }
223
+
224
+ /* User Profile Dropdown Styles */
225
+ .user-profile-menu {
226
+ position: fixed;
227
+ top: 20px;
228
+ right: 30px;
229
+ z-index: 1001;
230
+ display: flex;
231
+ align-items: center;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .profile-icon-btn {
236
+ width: 42px;
237
+ height: 42px;
238
+ border-radius: 50%;
239
+ background: linear-gradient(135deg, #2563eb, #1e40af);
240
+ display: flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ color: white;
244
+ font-size: 1.2rem;
245
+ border: 2px solid rgba(255, 255, 255, 0.4);
246
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
247
+ box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
248
+ }
249
+
250
+ .profile-icon-btn:hover {
251
+ transform: scale(1.1);
252
+ border-color: white;
253
+ box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4);
254
+ }
255
+
256
+ .dropdown-menu {
257
+ position: absolute;
258
+ top: 120%;
259
+ right: 0;
260
+ width: 200px;
261
+ background: white;
262
+ border-radius: 12px;
263
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
264
+ overflow: hidden;
265
+ display: none;
266
+ flex-direction: column;
267
+ border: 1px solid rgba(37, 99, 235, 0.1);
268
+ animation: dropdownFade 0.3s ease;
269
+ }
270
+
271
+ @keyframes dropdownFade {
272
+ from {
273
+ opacity: 0;
274
+ transform: translateY(-10px);
275
+ }
276
+
277
+ to {
278
+ opacity: 1;
279
+ transform: translateY(0);
280
+ }
281
+ }
282
+
283
+ .dropdown-menu.show {
284
+ display: flex;
285
+ }
286
+
287
+ .dropdown-item {
288
+ padding: 12px 20px;
289
+ font-size: 0.95rem;
290
+ color: #1e293b;
291
+ text-decoration: none;
292
+ display: flex;
293
+ align-items: center;
294
+ gap: 10px;
295
+ transition: background 0.2s;
296
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
297
+ }
298
+
299
+ .dropdown-item:last-child {
300
+ border-bottom: none;
301
+ }
302
+
303
+ .dropdown-item:hover {
304
+ background: rgba(37, 99, 235, 0.05);
305
+ }
306
+
307
+ .dropdown-item.role-info {
308
+ font-weight: 600;
309
+ color: #2563eb;
310
+ background: rgba(37, 99, 235, 0.05);
311
+ cursor: default;
312
+ }
313
+
314
+ .logout-action {
315
+ color: #dc2626;
316
+ font-weight: 500;
317
+ }
318
+
319
+ .logout-action:hover {
320
+ background: rgba(220, 38, 38, 0.05);
321
+ }
322
+ </style>
323
+ </head>
324
+
325
+ <body>
326
+ <div class="sidebar">
327
+ <h2>Law Student</h2>
328
+ <a href="#" onclick="showSidebarContent('laws')">📜 Indian Laws</a>
329
+ <a href="#" onclick="showSidebarContent('materials')">📚 Study Materials</a>
330
+ <a href="#" onclick="showSidebarContent('cases')">🧠 Case Studies</a>
331
+ <a href="#" onclick="showSidebarContent('events')">📅 Legal Events</a>
332
+ <a href="#" onclick="showSidebarContent('chatbot')">⚖️ Legal Chatbot</a>
333
+
334
+
335
+
336
+ </div>
337
+ <video autoplay loop muted plays-inline class="back-video">
338
+ <source src="/static/video.mp4" type="video/mp4">
339
+ </video>
340
+
341
+ <div class="user-profile-menu" id="userProfileMenu">
342
+ <div class="profile-icon-btn">
343
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
344
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
345
+ <circle cx="12" cy="7" r="4"></circle>
346
+ </svg>
347
+ </div>
348
+ <div class="dropdown-menu" id="dropdownMenu">
349
+ <div class="dropdown-item role-info">
350
+ Role: Law Student
351
+ </div>
352
+ <a href="#" class="dropdown-item logout-action" onclick="logout()">
353
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
354
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
355
+ <polyline points="16 17 21 12 16 7"></polyline>
356
+ <line x1="21" y1="12" x2="9" y2="12"></line>
357
+ </svg>
358
+ Logout
359
+ </a>
360
+ </div>
361
+ </div>
362
+
363
+ <div class="main">
364
+ <div class="header">Welcome to Your Legal Learning Dashboard</div>
365
+
366
+ <div class="cards">
367
+ <div class="card" onmouseover="showContent('fundamental')">
368
+ <h3>Fundamental Rights</h3>
369
+ <p>Explore key constitutional rights every citizen and law student must know.</p>
370
+ </div>
371
+ <div class="card" onmouseover="showContent('ipc')">
372
+ <h3>IPC Essentials</h3>
373
+ <p>Learn major sections of the Indian Penal Code through visual explanations.</p>
374
+ </div>
375
+ <div class="card" onmouseover="showContent('judgments')">
376
+ <h3>Recent Judgments</h3>
377
+ <p>Stay updated with landmark verdicts and their implications.</p>
378
+ </div>
379
+ <div class="card" onmouseover="showContent('notes')">
380
+ <h3>Downloadable Notes</h3>
381
+ <p>Access law subject notes, PDF guides, and court practice tips.</p>
382
+ </div>
383
+ </div>
384
+
385
+ <div id="content-area"></div>
386
+ </div>
387
+
388
+ <button class="chatbot-btn" onclick="openChatbot()">💬 Ask Legal Bot</button>
389
+
390
+
391
+
392
+ <script>
393
+ // User Profile Dropdown
394
+ const userProfileMenu = document.getElementById('userProfileMenu');
395
+ const dropdownMenu = document.getElementById('dropdownMenu');
396
+
397
+ userProfileMenu.addEventListener('click', (e) => {
398
+ e.stopPropagation();
399
+ dropdownMenu.classList.toggle('show');
400
+ });
401
+
402
+ document.addEventListener('click', () => {
403
+ if (dropdownMenu.classList.contains('show')) {
404
+ dropdownMenu.classList.remove('show');
405
+ }
406
+ });
407
+
408
+ function openChatbot() {
409
+ alert(" Launching the chatbot... just a sec!.");
410
+ window.open("studentchatbot.html", "_blank");
411
+ }
412
+
413
+ function logout() {
414
+ if (window.dashboardEntrance) {
415
+ window.dashboardEntrance.logout();
416
+ } else {
417
+ localStorage.removeItem('token');
418
+ window.location.href = '/role';
419
+ }
420
+ }
421
+
422
+ function goBack() {
423
+ window.history.back();
424
+ }
425
+ function hideContent() {
426
+ document.getElementById("content-area").style.display = "none";
427
+ }
428
+
429
+
430
+ function showContent(type) {
431
+ const contentArea = document.getElementById("content-area");
432
+ contentArea.style.display = "block";
433
+ let content = '';
434
+
435
+ if (type === 'fundamental') {
436
+ content = `
437
+ <h2>Fundamental Rights in India</h2>
438
+ <ul>
439
+ <li><strong>Right to Equality:</strong> Article 14–18</li>
440
+ <li><strong>Right to Freedom:</strong> Article 19–22</li>
441
+ <li><strong>Right against Exploitation:</strong> Article 23–24</li>
442
+ <li><strong>Right to Freedom of Religion:</strong> Article 25–28</li>
443
+ <li><strong>Cultural and Educational Rights:</strong> Article 29–30</li>
444
+ <li><strong>Right to Constitutional Remedies:</strong> Article 32</li>
445
+ </ul>
446
+ `;
447
+ } else if (type === 'ipc') {
448
+ content = `
449
+ <h2>Important IPC Sections</h2>
450
+ <ul>
451
+ <li><strong>Section 302:</strong> Punishment for murder</li>
452
+ <li><strong>Section 375–376:</strong> Rape and related offences</li>
453
+ <li><strong>Section 420:</strong> Cheating and dishonestly inducing delivery of property</li>
454
+ <li><strong>Section 498A:</strong> Cruelty by husband or relatives</li>
455
+ </ul>
456
+ `;
457
+ } else if (type === 'judgments') {
458
+ content = `
459
+ <h2>Landmark Judgments</h2>
460
+ <ul>
461
+ <li><strong>Kesavananda Bharati v. State of Kerala:</strong> Introduced Basic Structure Doctrine.</li>
462
+ <li><strong>Maneka Gandhi v. Union of India:</strong> Expanded interpretation of Article 21.</li>
463
+ <li><strong>Navtej Singh Johar v. Union of India:</strong> Decriminalized Section 377.</li>
464
+ </ul>
465
+ `;
466
+ } else if (type === 'notes') {
467
+ content = `
468
+ <h2>Downloadable Law Notes</h2>
469
+ <ul>
470
+ <li><a href="static/2023050195.pdf">📄 Constitutional Law - PDF</a></li>
471
+ <li><a href="static/wipo_guide_ipc_2019.pdf">📄 IPC Visual Guide - PDF</a></li>
472
+ <li><a href="static/legal writing.pdf">📄 Legal Writing Tips - PDF</a></li>
473
+ </ul>
474
+ `;
475
+ }
476
+
477
+ contentArea.innerHTML = content;
478
+ }
479
+
480
+ function showSidebarContent(type) {
481
+ const contentArea = document.getElementById("content-area");
482
+ contentArea.style.display = "block";
483
+
484
+ let content = "";
485
+
486
+ if (type === "laws") {
487
+ content = `
488
+ <h2>Indian Laws Overview</h2>
489
+ <ul>
490
+ <li>📘 <strong>Constitution of India</strong> - Framework for governance and fundamental rights</li>
491
+ <li>🔍 <strong>IPC (Indian Penal Code)</strong> - Defines criminal offenses and punishments</li>
492
+ <li>📜 <strong>Civil Procedure Code</strong> - Procedure for civil litigation</li>
493
+ </ul>
494
+ `;
495
+ } else if (type === "materials") {
496
+ content = `
497
+ <h2>Study Materials</h2>
498
+ <ul>
499
+ <li><a href="https://www.lawhousekolkata.com/indian-law/" target="_blank">📝 Bare Acts of Major Laws</a></li>
500
+ <li><a href="static/LegalTheory_CaseLawNotes.pdf" target="_blank">📚 Legal Theory & Case Law Notes</a></li>
501
+ <li><a href="static/MootCourt_Advocacy.pdf" target="_blank">📁 Moot Court & Advocacy Skills</a></li>
502
+ </ul>
503
+ `;
504
+ } else if (type === "cases") {
505
+ content = `
506
+ <h2>Case Studies</h2>
507
+ <ul>
508
+ <li>📌 <strong>Vishaka v. State of Rajasthan</strong> - Sexual harassment guidelines</li>
509
+ <li>📌 <strong>Shreya Singhal v. Union of India</strong> - Freedom of speech online</li>
510
+ </ul>
511
+ `;
512
+ } else if (type === "events") {
513
+ content = `
514
+ <h2>Upcoming Legal Events</h2>
515
+ <ul>
516
+ <li>🗓️ National Moot Court Competition - May 2025</li>
517
+ <li>🗓️ Guest Lecture: Cyber Law - April 30, 2025</li>
518
+ <li>🗓️ Internship Fair - June 2025</li>
519
+ </ul>
520
+ `;
521
+ } else if (type === "chatbot") {
522
+ content = `
523
+ <h2>Legal Chatbot Assistant</h2>
524
+ <p>Interact with the AI legal assistant to clear your doubts regarding:</p>
525
+ <ul>
526
+ <li>⚖️ Fundamental Rights</li>
527
+ <li>⚖️ IPC/CrPC/Constitutional Law</li>
528
+ <li>⚖️ Landmark Judgments</li>
529
+ </ul>
530
+ <p>Click the "Ask Legal Bot" button below to get started.</p>
531
+ `;
532
+ }
533
+
534
+ contentArea.innerHTML = content;
535
+ }
536
+ </script>
537
+
538
+ </body>
539
+
540
+ </html>
src/apps/templates/viewall.html ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>All Cases</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ margin: 0;
11
+ padding: 0;
12
+ background-color: #f5f7fa;
13
+ color: #333;
14
+ line-height: 1.6;
15
+ }
16
+ .header {
17
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
18
+ color: white;
19
+ padding: 1rem 2rem;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ align-items: center;
23
+ }
24
+ .back-btn {
25
+ color: white;
26
+ text-decoration: none;
27
+ font-weight: 500;
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 0.5rem;
31
+ }
32
+ .container {
33
+ max-width: 1200px;
34
+ margin: 2rem auto;
35
+ padding: 0 1rem;
36
+ }
37
+ .case-container {
38
+ display: grid;
39
+ grid-template-columns: 1fr 1fr;
40
+ gap: 2rem;
41
+ margin-bottom: 3rem;
42
+ }
43
+ .questions, .answers {
44
+ background: white;
45
+ border-radius: 8px;
46
+ padding: 1.5rem;
47
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
48
+ }
49
+ .case-header {
50
+ border-bottom: 2px solid #e0e0e0;
51
+ padding-bottom: 1rem;
52
+ margin-bottom: 1rem;
53
+ }
54
+ .case-header h2 {
55
+ color: #1e3c72;
56
+ margin-bottom: 0.5rem;
57
+ }
58
+ .case-meta {
59
+ display: flex;
60
+ gap: 1rem;
61
+ color: #666;
62
+ font-size: 0.9rem;
63
+ }
64
+ .case-status {
65
+ display: inline-block;
66
+ padding: 0.3rem 0.8rem;
67
+ border-radius: 20px;
68
+ font-size: 0.8rem;
69
+ font-weight: 500;
70
+ margin-bottom: 1rem;
71
+ }
72
+ .status-pending {
73
+ background-color: #fff3cd;
74
+ color: #856404;
75
+ }
76
+ .status-in-progress {
77
+ background-color: #cce5ff;
78
+ color: #004085;
79
+ }
80
+ .status-completed {
81
+ background-color: #d4edda;
82
+ color: #155724;
83
+ }
84
+ .qa-item {
85
+ margin-bottom: 1.5rem;
86
+ }
87
+ .question {
88
+ font-weight: 600;
89
+ margin-bottom: 0.5rem;
90
+ color: #1e3c72;
91
+ }
92
+ .answer {
93
+ color: #555;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <header class="header">
99
+ <h1>All Cases - Detailed View</h1>
100
+ <a href="judgedashboard.html" class="back-btn">
101
+ <i class="fas fa-arrow-left"></i> Back to Dashboard
102
+ </a>
103
+ </header>
104
+
105
+ <div class="container">
106
+ <!-- Case 1 -->
107
+ <div class="case-container">
108
+ <div class="questions">
109
+ <div class="case-header">
110
+ <h2>State vs. Johnson</h2>
111
+ <div class="case-meta">
112
+ <span># CR-2024-0456</span>
113
+ <span>10:30 AM</span>
114
+ </div>
115
+ <span class="case-status status-pending">Pending Judgment</span>
116
+ <p>Criminal case involving charges of fraud and embezzlement. Defense has filed motion to suppress.</p>
117
+ </div>
118
+
119
+ <div class="qa-item">
120
+ <div class="question">Q: What is the main argument for the motion to suppress?</div>
121
+ </div>
122
+
123
+ <div class="qa-item">
124
+ <div class="question">Q: What precedent cases are relevant here?</div>
125
+ </div>
126
+
127
+ <div class="qa-item">
128
+ <div class="question">Q: When is the next hearing scheduled?</div>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="answers">
133
+ <div class="case-header">
134
+ <h2>Case Responses</h2>
135
+ </div>
136
+
137
+ <div class="qa-item">
138
+ <div class="question">Q: What is the main argument for the motion to suppress?</div>
139
+ <div class="answer">A: The defense argues the evidence was obtained without proper warrant authorization and cites violation of the defendant's Fourth Amendment rights.</div>
140
+ </div>
141
+
142
+ <div class="qa-item">
143
+ <div class="question">Q: What precedent cases are relevant here?</div>
144
+ <div class="answer">A: Key precedents include:<br>
145
+ - Smith v. Maryland (1979)<br>
146
+ - United States v. Jones (2012)<br>
147
+ - Carpenter v. United States (2018)
148
+ </div>
149
+ </div>
150
+
151
+ <div class="qa-item">
152
+ <div class="question">Q: When is the next hearing scheduled?</div>
153
+ <div class="answer">A: The motion hearing is scheduled for April 15, 2024 at 9:30 AM in Courtroom 4B.</div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <!-- Case 2 -->
159
+ <div class="case-container">
160
+ <div class="questions">
161
+ <div class="case-header">
162
+ <h2>Doe vs. Smith Corporation</h2>
163
+ <div class="case-meta">
164
+ <span># CV-2024-0789</span>
165
+ <span>02:15 PM</span>
166
+ </div>
167
+ <span class="case-status status-in-progress">In Progress</span>
168
+ <p>Civil lawsuit regarding wrongful termination. Plaintiff seeking damages of $2.5 million.</p>
169
+ </div>
170
+
171
+ <div class="qa-item">
172
+ <div class="question">Q: What are the plaintiff's main allegations?</div>
173
+ </div>
174
+
175
+ <div class="qa-item">
176
+ <div class="question">Q: What evidence supports the wrongful termination claim?</div>
177
+ </div>
178
+ </div>
179
+
180
+ <div class="answers">
181
+ <div class="case-header">
182
+ <h2>Case Responses</h2>
183
+ </div>
184
+
185
+ <div class="qa-item">
186
+ <div class="question">Q: What are the plaintiff's main allegations?</div>
187
+ <div class="answer">A: The plaintiff alleges:<br>
188
+ - Termination without cause after 12 years of service<br>
189
+ - Age discrimination (plaintiff was 58 at termination)<br>
190
+ - Retaliation for whistleblowing on safety violations
191
+ </div>
192
+ </div>
193
+
194
+ <div class="qa-item">
195
+ <div class="question">Q: What evidence supports the wrongful termination claim?</div>
196
+ <div class="answer">A: Key evidence includes:<br>
197
+ - Performance reviews showing consistent "exceeds expectations" ratings<br>
198
+ - Emails discussing the plaintiff's age and "need for fresh blood"<br>
199
+ - DOL complaint filed 3 months prior to termination
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- Case 3 -->
206
+ <div class="case-container">
207
+ <div class="questions">
208
+ <div class="case-header">
209
+ <h2>In re: Peterson Estate</h2>
210
+ <div class="case-meta">
211
+ <span># PR-2024-0123</span>
212
+ <span>11:00 AM</span>
213
+ </div>
214
+ <span class="case-status status-completed">Completed</span>
215
+ <p>Probate matter with dispute among heirs regarding distribution of assets.</p>
216
+ </div>
217
+
218
+ <div class="qa-item">
219
+ <div class="question">Q: What was the final distribution ruling?</div>
220
+ </div>
221
+
222
+ <div class="qa-item">
223
+ <div class="question">Q: Were there any contested items?</div>
224
+ </div>
225
+ </div>
226
+
227
+ <div class="answers">
228
+ <div class="case-header">
229
+ <h2>Case Responses</h2>
230
+ </div>
231
+
232
+ <div class="qa-item">
233
+ <div class="question">Q: What was the final distribution ruling?</div>
234
+ <div class="answer">A: The estate was distributed as follows:<br>
235
+ - 50% to surviving spouse<br>
236
+ - 25% to daughter from first marriage<br>
237
+ - 25% to son from first marriage<br>
238
+ Special bequest of family heirlooms as specified in addendum
239
+ </div>
240
+ </div>
241
+
242
+ <div class="qa-item">
243
+ <div class="question">Q: Were there any contested items?</div>
244
+ <div class="answer">A: The vacation property was initially contested but was awarded to the surviving spouse with right of first refusal given to the children if sold.</div>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Font Awesome for icons -->
251
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
252
+ </body>
253
+ </html>