Spaces:
Running
Running
Upload 78 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +6 -0
- Dockerfile +32 -0
- requirements.txt +25 -0
- src/apps/.env +26 -0
- src/apps/__pycache__/app.cpython-312.pyc +0 -0
- src/apps/__pycache__/auth.cpython-312.pyc +0 -0
- src/apps/__pycache__/database.cpython-312.pyc +0 -0
- src/apps/__pycache__/email_utils.cpython-312.pyc +0 -0
- src/apps/__pycache__/models.cpython-312.pyc +0 -0
- src/apps/app.py +520 -0
- src/apps/auth.py +42 -0
- src/apps/database.py +40 -0
- src/apps/email_utils.py +65 -0
- src/apps/models.py +32 -0
- src/apps/static/2023050195.pdf +3 -0
- src/apps/static/LegalTheory_CaseLawNotes.pdf +3 -0
- src/apps/static/MootCourt_Advocacy.pdf +3 -0
- src/apps/static/css/entrance.css +125 -0
- src/apps/static/css/modern-chat.css +609 -0
- src/apps/static/css/styles.css +87 -0
- src/apps/static/images/favicon.png +0 -0
- src/apps/static/images/judge.jpg +0 -0
- src/apps/static/images/lawbackground.jpg +0 -0
- src/apps/static/images/woman.jpeg +0 -0
- src/apps/static/js/entrance.js +132 -0
- src/apps/static/js/roleselection.js +34 -0
- src/apps/static/legal writing.pdf +3 -0
- src/apps/static/video.mp4 +3 -0
- src/apps/static/wipo_guide_ipc_2019.pdf +3 -0
- src/apps/templates/FIR.html +192 -0
- src/apps/templates/Judgechatbot.html +875 -0
- src/apps/templates/advocatechatbot.html +894 -0
- src/apps/templates/advocatedashboard.html +440 -0
- src/apps/templates/advocateresources.html +297 -0
- src/apps/templates/citizen.html +791 -0
- src/apps/templates/forgot_password.html +119 -0
- src/apps/templates/judgecalender.html +164 -0
- src/apps/templates/judgedashboard.html +697 -0
- src/apps/templates/legalrights.html +186 -0
- src/apps/templates/login.html +295 -0
- src/apps/templates/minor.html +897 -0
- src/apps/templates/register.html +139 -0
- src/apps/templates/reset_password.html +137 -0
- src/apps/templates/resources.html +213 -0
- src/apps/templates/roleselection.html +51 -0
- src/apps/templates/safetytips.html +186 -0
- src/apps/templates/script.js +42 -0
- src/apps/templates/studentchatbot.html +855 -0
- src/apps/templates/studentdashboard.html +540 -0
- 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 |
+
© 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 |
+
© 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 |
+
© 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 |
+
© 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 |
+
© 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 |
+
© 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 |
+
© 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>
|