Skip to content

Commit

Permalink
created schema
Browse files Browse the repository at this point in the history
  • Loading branch information
shubham-attri committed Dec 22, 2024
1 parent 7682c5c commit f904a45
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 403 deletions.
5 changes: 4 additions & 1 deletion architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,7 @@ This architecture provides a secure, scalable, and feature-rich platform for a l
• FastAPI and Langchain-based ChatAnthropic handle the AI conversation flow, context retrieval, and streaming answers.
• The Next.js 14 front-end offers an accessible dual-mode interface, either for broad legal research or for case-specific operations—both integrated into an “Open Canvas” style framework to handle complex workflows.

By structuring the project in this step-by-step fashion, each critical part (chat system, document management, case handling, advanced search, and user security) interlocks tightly to support a professional legal workflow with powerful AI capabilities.
By structuring the project in this step-by-step fashion, each critical part (chat system, document management, case handling, advanced search, and user security) interlocks tightly to support a professional legal workflow with powerful AI capabilities.



Binary file modified backend/app/api/v1/__pycache__/documents.cpython-39.pyc
Binary file not shown.
70 changes: 35 additions & 35 deletions backend/app/api/v1/documents.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from fastapi import APIRouter, Depends, File, UploadFile, HTTPException
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from typing import Optional, List
from app.core.security import get_current_user
from app.models.auth import User
from app.models.database import Document
from app.models.document import Document
from app.services.storage import StorageService
import mimetypes

Expand All @@ -12,58 +12,58 @@
@router.post("/upload")
async def upload_document(
file: UploadFile = File(...),
case_id: Optional[str] = None,
chat_session_id: Optional[str] = None,
case_id: Optional[str] = Form(None),
chat_session_id: Optional[str] = Form(None),
current_user: User = Depends(get_current_user)
):
"""Upload a document"""
try:
# Guess file type from extension
file_type, _ = mimetypes.guess_type(file.filename)
if not file_type:
file_type = "application/octet-stream"

document = await storage_service.upload_document(
file_type = mimetypes.guess_type(file.filename)[0] or "application/octet-stream"

document = storage_service.upload_document(
file=file.file,
filename=file.filename,
user_id=str(current_user.id),
user_id=current_user.id,
file_type=file_type,
case_id=case_id,
chat_session_id=chat_session_id
)

return document

except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to upload document: {str(e)}"
)

@router.get("/{document_id}")
async def get_document(
document_id: str,
current_user: User = Depends(get_current_user)
):
"""Get document details and download URL"""
document = await storage_service.get_document(document_id)
if not document:
raise HTTPException(status_code=404, detail="Document not found")
return document
raise HTTPException(status_code=500, detail=str(e))

@router.get("/list")
async def list_documents(
case_id: Optional[str] = None,
chat_session_id: Optional[str] = None,
current_user: User = Depends(get_current_user)
):
"""List documents for user/case/chat session"""
documents = await storage_service.list_documents(
user_id=str(current_user.id),
case_id=case_id,
chat_session_id=chat_session_id
)
return documents
"""List documents for current user"""
try:
documents = storage_service.list_documents(
user_id=current_user.id,
case_id=case_id,
chat_session_id=chat_session_id
)
return documents
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

@router.get("/{document_id}")
async def get_document(
document_id: str,
current_user: User = Depends(get_current_user)
):
"""Get a specific document"""
try:
document = await storage_service.get_document(document_id)
if not document:
raise HTTPException(status_code=404, detail="Document not found")
if document.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Not authorized to access this document")
return document
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

@router.delete("/{document_id}")
async def delete_document(
Expand Down
Binary file modified backend/app/core/__pycache__/config.cpython-39.pyc
Binary file not shown.
Binary file not shown.
18 changes: 18 additions & 0 deletions backend/app/models/document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class Document(BaseModel):
id: str
user_id: str
title: str
storage_path: str
file_type: str
file_size: int
case_id: Optional[str] = None
session_id: Optional[str] = None
content: Optional[str] = None
content_vector: Optional[list[float]] = None
created_at: str
updated_at: Optional[str] = None
metadata: Optional[dict] = None
Binary file modified backend/app/services/__pycache__/storage.cpython-39.pyc
Binary file not shown.
97 changes: 46 additions & 51 deletions backend/app/services/storage.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from typing import Optional, BinaryIO, List
from typing import BinaryIO, Optional
from app.models.document import Document
from app.services.supabase import get_supabase_client
from app.models.database import Document
import uuid
from datetime import datetime
import uuid

class StorageService:
def __init__(self):
self.supabase = get_supabase_client()
self.bucket_name = "documents"

async def _ensure_bucket_exists(self):
def _ensure_bucket_exists(self):
"""Ensure the storage bucket exists"""
try:
await self.supabase.storage.get_bucket(self.bucket_name)
except:
await self.supabase.storage.create_bucket(self.bucket_name)
buckets = self.supabase.storage.list_buckets()
if not any(b['name'] == self.bucket_name for b in buckets):
self.supabase.storage.create_bucket(self.bucket_name, {'public': False})
except Exception as e:
# Create bucket if it doesn't exist
self.supabase.storage.create_bucket(self.bucket_name, {'public': False})

async def upload_document(
def upload_document(
self,
file: BinaryIO,
filename: str,
Expand All @@ -28,72 +31,64 @@ async def upload_document(
) -> Document:
"""Upload document to Supabase Storage and create database entry"""

await self._ensure_bucket_exists()
self._ensure_bucket_exists()

# Generate unique storage path
storage_path = f"{user_id}/{str(uuid.uuid4())}/{filename}"

# Upload to storage
await self.supabase.storage.from_(self.bucket_name).upload(
storage_path,
file
# Upload to storage using from_ method
self.supabase.storage.from_(self.bucket_name).upload(
path=storage_path,
file=file,
file_options={"content-type": file_type}
)

# Create database entry
document = Document(
id=uuid.uuid4(),
user_id=user_id,
name=filename,
type=file_type,
size=file.tell(),
storage_path=storage_path,
case_id=case_id,
chat_session_id=chat_session_id,
created_at=datetime.utcnow(),
metadata=metadata
)
document = {
"id": str(uuid.uuid4()),
"user_id": user_id,
"title": filename,
"file_type": file_type,
"file_size": file.tell(),
"storage_path": storage_path,
"case_id": case_id,
"session_id": chat_session_id,
"created_at": datetime.utcnow().isoformat(),
"metadata": metadata
}

await self.supabase.table("documents").insert(document.dict())
return document
result = self.supabase.table("documents").insert(document).execute()
return Document(**result.data[0])

async def get_document(self, document_id: str) -> Optional[Document]:
"""Get document metadata and generate download URL"""
result = await self.supabase.table("documents").select("*").eq("id", document_id).single()

if not result:
return None

document = Document(**result)

# Generate temporary download URL
url = self.supabase.storage.from_(self.bucket_name).get_public_url(document.storage_path)
document.metadata = document.metadata or {}
document.metadata["download_url"] = url

return document
def get_document(self, document_id: str) -> Optional[Document]:
"""Get document by ID"""
result = self.supabase.table("documents").select("*").eq("id", document_id).execute()
if result.data:
return Document(**result.data[0])
return None

async def list_documents(
def list_documents(
self,
user_id: str,
case_id: Optional[str] = None,
chat_session_id: Optional[str] = None
) -> List[Document]:
"""List documents for user/case/chat session"""
) -> list[Document]:
"""List documents for user, optionally filtered by case or chat session"""
query = self.supabase.table("documents").select("*").eq("user_id", user_id)

if case_id:
query = query.eq("case_id", case_id)
if chat_session_id:
query = query.eq("chat_session_id", chat_session_id)
query = query.eq("session_id", chat_session_id)

result = await query.execute()
result = query.execute()
return [Document(**doc) for doc in result.data]

async def delete_document(self, document_id: str):
def delete_document(self, document_id: str):
"""Delete document from storage and database"""
document = await self.get_document(document_id)
document = self.get_document(document_id)
if document:
# Delete from storage
await self.supabase.storage.from_(self.bucket_name).remove([document.storage_path])
self.supabase.storage.from_(self.bucket_name).remove([document.storage_path])
# Delete from database
await self.supabase.table("documents").delete().eq("id", document_id)
self.supabase.table("documents").delete().eq("id", document_id).execute()
27 changes: 26 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@

[tool.poetry]
name = "agent-binod"
version = "0.1.0"
description = "Legal AI Assistant Backend"
authors = ["Your Name <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
fastapi = "^0.109.2"
uvicorn = {extras = ["standard"], version = "^0.27.1"}
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
redis = "5.0.1"
anthropic = "0.18.1"
langchain = "0.1.6"
langchain-anthropic = "0.1.1"
langchain-core = "0.1.22"
python-multipart = "^0.0.9"
pydantic = "^2.6.1"
pydantic-settings = "2.1.0"
python-dotenv = "1.0.0"
httpx = "0.25.2"
supabase = "2.3.4"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
24 changes: 13 additions & 11 deletions backend/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
version="0.1.0",
packages=find_packages(),
install_requires=[
"fastapi==0.109.2",
"uvicorn[standard]==0.27.1",
"python-jose[cryptography]==3.3.0",
"python-multipart==0.0.9",
"pydantic==2.6.1",
"pydantic-settings==2.1.0",
"supabase==1.2.0",
"httpx==0.24.1",
"python-dotenv==1.0.1",
"fastapi>=0.109.2",
"uvicorn[standard]>=0.27.1",
"python-jose[cryptography]>=3.3.0",
"redis==5.0.1",
"anthropic==0.18.1",
"langchain==0.1.0",
"langchain-anthropic==0.1.1"
"langchain==0.1.6",
"langchain-anthropic==0.1.1",
"langchain-core==0.1.22",
"python-multipart>=0.0.9",
"pydantic>=2.6.1",
"pydantic-settings==2.1.0",
"python-dotenv==1.0.0",
"httpx==0.25.2",
"supabase-py==2.3.4"
],
python_requires=">=3.9",
)
Loading

0 comments on commit f904a45

Please sign in to comment.