Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
__pycache__/
*.py[cod]
*$py.class
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.vscode/
.idea/
.git/
.gitignore
*.db
faiss_index.bin
faiss_meta.pkl
node_modules/
frontend/
35 changes: 35 additions & 0 deletions Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Use an official Python runtime as a parent image
FROM python:3.10-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PORT=8000

# Set work directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
libgomp1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy project files
COPY app/ ./app/
COPY data/ ./data/
COPY legal_ai.db .
COPY faiss_index.bin .
COPY faiss_meta.pkl .

# Expose the port the app runs on
EXPOSE 8000

# Command to run the application
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,13 @@ It does **NOT replace a lawyer**, but acts as a **first step guidance system**,
```
legal-ai/
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── context/
│ │ ├── pages/
│ │ └── services/
├── backend/
│ ├── app/
│ ├── routes/
│ └── chat.py
├── .gitignore
├── app/ # FastAPI backend
├── frontend/ # React + Vite frontend
├── data/ # Data files
├── requirements.txt # Backend dependencies
├── docker-compose.yml # Docker orchestration
├── Dockerfile.backend # Backend Docker configuration
├── .dockerignore # Docker ignore rules
└── README.md
```

Expand Down Expand Up @@ -162,9 +156,25 @@ npm install
npm run dev
```


---

## 🌍 Environment Variables
### 🔹 4. Docker Setup (Recommended)

If you have Docker and Docker Compose installed, you can run the entire stack with a single command:

```bash
# Build and start the containers
docker-compose up --build
```

The application will be available at:
* Frontend: http://localhost:3000
* Backend: http://localhost:8000

**Note:** Make sure to create a `.env` file in the root directory with your `GROQ_API_KEY` before running Docker.

---

### Backend

Expand Down
10 changes: 9 additions & 1 deletion app/api/chat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Request, Depends
from pydantic import BaseModel

from app.services.memory import add_message, get_history
from app.services.memory import add_message, get_history, clear_history
from app.services.intent import detect_intent
from app.services.llm import query_groq_llm
from app.services.safety import validate_response
Expand All @@ -11,6 +11,14 @@
router = APIRouter()


@router.delete("/chat/{session_id}")
async def delete_chat(session_id: str, user=Depends(get_current_user)):
user_id = user.id
full_session_id = f"{user_id}-{session_id}"
clear_history(full_session_id)
return {"message": "Chat history cleared successfully"}


class ChatRequest(BaseModel):
message: str
session_id: str = "default-session"
Expand Down
6 changes: 6 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ async def lifespan(app: FastAPI):
except Exception as e:
logger.warning(f"DB init failed: {e}")

# Check for critical environment variables
if not os.getenv("GROQ_API_KEY"):
logger.error("❌ GROQ_API_KEY is missing! Backend will use fallback responses.")
if not os.getenv("HF_TOKEN"):
logger.warning("⚠️ HF_TOKEN is missing! HuggingFace API may fail or be rate-limited.")

try:
vector_store.load_index()
logger.info("✅ FAISS index loaded")
Expand Down
75 changes: 10 additions & 65 deletions app/services/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@ def query_groq_llm(
api_key = os.getenv("GROQ_API_KEY")

if not api_key:
raise ValueError("GROQ_API_KEY not set")
logger.error("GROQ_API_KEY is not set")
return fallback_response("english")

# Detect style
style = detect_language_style(user_query)

# ✅ NEW: Build context from FAISS
# ✅ Build context from FAISS
context = ""
if retrieved_chunks:
context = "\n\n".join(retrieved_chunks[:5])

# ✅ UPDATED system prompt (RAG aware)
# ✅ System prompt (RAG aware)
system_prompt = f"""
You are a legal awareness assistant for Indian users.

Expand Down Expand Up @@ -99,8 +100,7 @@ def query_groq_llm(
Conversation History:
{history}

User Query:
{user_query}
User Query: {user_query}
"""

payload = {
Expand All @@ -110,7 +110,7 @@ def query_groq_llm(
{"role": "user", "content": user_prompt},
],
"temperature": 0.3,
"max_tokens": 500,
"max_tokens": 800,
}

headers = {
Expand All @@ -129,79 +129,24 @@ def query_groq_llm(
)

if response.status_code != 200:
logger.error(f"Groq API Error: {response.text}")
logger.error(f"Groq API Error: {response.status_code} - {response.text}")
return fallback_response(style)

data = response.json()

if "choices" not in data or not data["choices"]:
logger.error(f"Invalid response structure from Groq: {data}")
return fallback_response(style)

answer = data["choices"][0]["message"]["content"].strip()
return answer if answer else fallback_response(style)

except Exception as e:
logger.error(f"Groq request failed: {e}")
return fallback_response(style)

# User prompt
user_prompt = f"""
User communication style: {style}

Conversation History:
{history}

User Query:
{user_query}
"""

payload = {
"model": MODEL_NAME,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.3,
"max_tokens": 500,
}

headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}

# ─────────────────────────────
# API CALL
# ─────────────────────────────
try:
logger.info(f"Calling Groq... (Style: {style})")

response = requests.post(
GROQ_API_URL,
headers=headers,
json=payload,
timeout=30,
)

if response.status_code != 200:
logger.error(f"Groq API Error: {response.text}")
return fallback_response(style)

data = response.json()

if "choices" not in data or not data["choices"]:
logger.error(f"Invalid response: {data}")
return fallback_response(style)

answer = data["choices"][0]["message"]["content"].strip()


if not answer:
return fallback_response(style)

return answer

except Exception as e:
logger.error(f"Groq request failed: {e}")
logger.error(f"Groq request failed: {str(e)}")
return fallback_response(style)


Expand Down
24 changes: 22 additions & 2 deletions app/services/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,39 @@


def add_message(session_id: str, role: str, content: str):
# Save to DB for persistence
save_message(session_id, role, content)

# Also update in-memory cache
conversation_store[session_id].append({
"role": role,
"content": content
})

# keep only last N messages
# keep only last N messages in memory
if len(conversation_store[session_id]) > MAX_HISTORY:
conversation_store[session_id] = conversation_store[session_id][-MAX_HISTORY:]


def get_history(session_id: str):
# If not in cache, try to load from DB
if not conversation_store[session_id]:
db_messages = get_messages(session_id)
if db_messages:
conversation_store[session_id] = db_messages[-MAX_HISTORY:]

return conversation_store[session_id]


def clear_history(session_id: str):
conversation_store[session_id] = []
# Clear cache
conversation_store[session_id] = []

# Clear DB
from app.services.database import DB_PATH
import sqlite3
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("DELETE FROM messages WHERE session_id = ?", (session_id,))
conn.commit()
conn.close()
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
ports:
- "8000:8000"
volumes:
- .env:/app/.env
- ./legal_ai.db:/app/legal_ai.db
- ./faiss_index.bin:/app/faiss_index.bin
- ./faiss_meta.pkl:/app/faiss_meta.pkl
environment:
- PORT=8000
restart: always

frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
depends_on:
- backend
restart: always
9 changes: 9 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
dist/
.env
.npmrc
*.log
.vscode/
.idea/
.git/
.gitignore
26 changes: 26 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Stage 1: Build the React application
FROM node:18-alpine AS build

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install

# Copy the rest of the application code
COPY . .

# Build the application
RUN npm run build

# Stage 2: Serve the application using Nginx
FROM nginx:stable-alpine

# Copy the build output from the previous stage
COPY --from=build /app/dist /usr/share/nginx/html

# Expose port 80
EXPOSE 80

# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
2 changes: 1 addition & 1 deletion frontend/src/components/chat/ChatWindow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default function ChatWindow() {

try {
const token = await getToken()
const data = await sendMessageToBackend(query, token)
const data = await sendMessageToBackend(query, token, chatId)

const aiMsg = {
id: uuidv4(),
Expand Down
Loading