aymanemalih commited on
Commit
91ca409
Β·
verified Β·
1 Parent(s): 9d97405

Upload 25 files

Browse files
app/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ from flask_bcrypt import Bcrypt
4
+ from flask_jwt_extended import JWTManager
5
+
6
+ db = SQLAlchemy()
7
+ bcrypt = Bcrypt()
8
+ jwt = JWTManager()
9
+
10
+ def create_app():
11
+ app = Flask(__name__)
12
+ app.config.from_object('app.config.Config')
13
+
14
+ db.init_app(app)
15
+ bcrypt.init_app(app)
16
+ jwt.init_app(app)
17
+
18
+ from app.routes import auth, chat, admin, profile
19
+ app.register_blueprint(auth.bp)
20
+ app.register_blueprint(chat.bp)
21
+ app.register_blueprint(admin.bp)
22
+ app.register_blueprint(profile.bp)
23
+
24
+ return app
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (1.33 kB). View file
 
app/__pycache__/config.cpython-313.pyc ADDED
Binary file (665 Bytes). View file
 
app/__pycache__/decorators.cpython-313.pyc ADDED
Binary file (1.4 kB). View file
 
app/__pycache__/models.cpython-313.pyc ADDED
Binary file (3.09 kB). View file
 
app/config.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ class Config:
2
+ SECRET_KEY = 'Hala_Madrid'
3
+ JWT_SECRET_KEY = 'Hala_Madrid'
4
+ SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:@localhost/loimaroc'
5
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
6
+ PERSIST_DIR = "chroma_storage"
7
+ EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
app/decorators.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import wraps
2
+ from flask_jwt_extended import verify_jwt_in_request, get_jwt
3
+ from flask import jsonify
4
+
5
+ def roles_required(*roles):
6
+ def wrapper(fn):
7
+ @wraps(fn)
8
+ def decorator(*args, **kwargs):
9
+ verify_jwt_in_request()
10
+ claims = get_jwt()
11
+ user_roles = claims.get("roles", [])
12
+ if not any(role in roles for role in user_roles):
13
+ return jsonify(msg="Forbidden: insufficient permissions"), 403
14
+ return fn(*args, **kwargs)
15
+ return decorator
16
+ return wrapper
app/models.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import db
2
+ from datetime import datetime
3
+
4
+ user_roles = db.Table('user_role',
5
+ db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
6
+ db.Column('role_id', db.Integer, db.ForeignKey('role.id'))
7
+ )
8
+
9
+ class User(db.Model):
10
+ __tablename__ = 'users'
11
+ id = db.Column(db.Integer, primary_key=True)
12
+ first_name = db.Column(db.String(150))
13
+ last_name = db.Column(db.String(150))
14
+ email = db.Column(db.String(255), unique=True, nullable=False)
15
+ password = db.Column(db.String(255), nullable=False)
16
+ roles = db.relationship('Role', secondary=user_roles, backref='users')
17
+
18
+ class Role(db.Model):
19
+ id = db.Column(db.Integer, primary_key=True)
20
+ name = db.Column(db.String(50), unique=True, nullable=False)
21
+
22
+ class Question(db.Model):
23
+ __tablename__ = 'questions'
24
+ id = db.Column(db.Integer, primary_key=True)
25
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
26
+ content = db.Column(db.Text, nullable=False)
27
+ date = db.Column(db.DateTime, default=datetime.utcnow)
28
+ status = db.Column(db.String(20), default='pending')
29
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
30
+ user = db.relationship('User', backref=db.backref('questions', lazy=True))
app/routes/__init__.py ADDED
File without changes
app/routes/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (168 Bytes). View file
 
app/routes/__pycache__/admin.cpython-313.pyc ADDED
Binary file (1.09 kB). View file
 
app/routes/__pycache__/auth.cpython-313.pyc ADDED
Binary file (3.72 kB). View file
 
app/routes/__pycache__/chat.cpython-313.pyc ADDED
Binary file (2.26 kB). View file
 
app/routes/__pycache__/profile.cpython-313.pyc ADDED
Binary file (1.09 kB). View file
 
app/routes/admin.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, jsonify
2
+ from app.models import User
3
+ from app.decorators import roles_required
4
+
5
+ bp = Blueprint('admin', __name__, url_prefix='/admin')
6
+
7
+ @bp.route('/users', methods=['GET'])
8
+ @roles_required('admin')
9
+ def all_users():
10
+ users = User.query.all()
11
+ return jsonify([
12
+ {"id": u.id, "email": u.email, "roles": [r.name for r in u.roles]}
13
+ for u in users
14
+ ])
app/routes/auth.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.models import db, User, Role
3
+ from flask_bcrypt import generate_password_hash
4
+ from app import db, bcrypt
5
+ from flask_jwt_extended import create_access_token
6
+ from datetime import timedelta
7
+ bp = Blueprint('auth', __name__, url_prefix='/auth')
8
+
9
+ @bp.route('/register', methods=['POST'])
10
+ def register():
11
+ data = request.get_json()
12
+
13
+ email = data.get('email')
14
+ password = data.get('password')
15
+ first_name = data.get('first_name')
16
+ last_name = data.get('last_name')
17
+
18
+ if not email or not password:
19
+ return jsonify({"error": "Missing email or password"}), 400
20
+
21
+ if User.query.filter_by(email=email).first():
22
+ return jsonify({"error": "User already exists"}), 409
23
+
24
+ hashed_password = generate_password_hash(password).decode('utf-8')
25
+ user = User(email=email, password=hashed_password, first_name=first_name, last_name=last_name)
26
+
27
+ # ROLE LOGIC
28
+ if email == '[email protected]':
29
+ role = Role.query.filter_by(name='admin').first()
30
+ elif email == '[email protected]':
31
+ role = Role.query.filter_by(name='expert').first()
32
+ else:
33
+ role = Role.query.filter_by(name='client').first()
34
+
35
+ if not role:
36
+ return jsonify({"error": f"Role not found in DB"}), 500
37
+
38
+ user.roles.append(role)
39
+ db.session.add(user)
40
+ db.session.commit()
41
+
42
+ return jsonify({"message": "User registered successfully"}), 201
43
+ @bp.route('/login', methods=['POST'])
44
+ def login():
45
+ data = request.get_json()
46
+ email = data.get('email')
47
+ password = data.get('password')
48
+
49
+ if not email or not password:
50
+ return jsonify({"error": "Email and password are required"}), 400
51
+
52
+ user = User.query.filter_by(email=email).first()
53
+ if not user or not bcrypt.check_password_hash(user.password, password):
54
+ return jsonify({"error": "Invalid credentials"}), 401
55
+
56
+ access_token = create_access_token(identity=str(user.id), expires_delta=timedelta(days=1))
57
+ return jsonify({"access_token": access_token}), 200
app/routes/chat.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from flask_jwt_extended import jwt_required, get_jwt_identity
3
+ from app.services.chatbot import ask
4
+ from app.models import Question, db
5
+ from datetime import datetime
6
+
7
+ bp = Blueprint('chat', __name__, url_prefix='/chat')
8
+
9
+ @bp.route('/ask', methods=['POST'])
10
+ @jwt_required()
11
+ def ask_question():
12
+ user_id = str(get_jwt_identity())
13
+ data = request.get_json()
14
+ question_text = data.get('question')
15
+
16
+ if not question_text:
17
+ return jsonify({'error': 'Missing question'}), 400
18
+
19
+ question = Question(
20
+ user_id=user_id,
21
+ content=question_text,
22
+ status='pending'
23
+ )
24
+ db.session.add(question)
25
+ db.session.commit()
26
+
27
+ try:
28
+ k = int(data.get("k", 6)) # if k is missing, fallback to 6
29
+ results = ask(question_text, k=k)
30
+ question.status = 'answered'
31
+ except Exception as e:
32
+ results = []
33
+ question.status = 'error'
34
+
35
+ question.updated_at = datetime.utcnow()
36
+ db.session.commit()
37
+
38
+ return jsonify({
39
+ "question": {
40
+ "id": question.id,
41
+ "user_id": question.user_id,
42
+ "content": question.content,
43
+ "date": question.date.isoformat(),
44
+ "status": question.status,
45
+ "updated_at": question.updated_at.isoformat()
46
+ },
47
+ "results": results
48
+ }), 200
app/routes/profile.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, jsonify
2
+ from flask_jwt_extended import jwt_required, get_jwt_identity
3
+ from app.models import User
4
+
5
+ bp = Blueprint('profile', __name__, url_prefix='/profile')
6
+
7
+ @bp.route('/', methods=['GET'])
8
+ @jwt_required()
9
+ def profile():
10
+ user = User.query.get(get_jwt_identity())
11
+ return jsonify(
12
+ email=user.email,
13
+ name=f"{user.first_name} {user.last_name}",
14
+ roles=[r.name for r in user.roles]
15
+ )
app/scripts/load_documents.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from langchain.docstore.document import Document
4
+ from langchain_community.vectorstores import Chroma
5
+ from langchain_community.embeddings import HuggingFaceEmbeddings
6
+
7
+ # Configurations
8
+ DATA_DIR = "data"
9
+ PERSIST_DIR = "chroma_storage"
10
+ EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
11
+
12
+ def parse_pages(pages_str):
13
+ if pd.isna(pages_str):
14
+ return []
15
+ try:
16
+ pages = eval(str(pages_str))
17
+ return pages if isinstance(pages, list) else [pages]
18
+ except:
19
+ try:
20
+ return [int(p.strip()) for p in str(pages_str).split(",") if p.strip().isdigit()]
21
+ except:
22
+ return []
23
+
24
+ def load_csv(filepath: str) -> list:
25
+ df = pd.read_csv(filepath)
26
+ df.columns = [c.strip().lower() for c in df.columns]
27
+
28
+ documents = []
29
+ for _, row in df.iterrows():
30
+ text = row.get('contenu', '')
31
+ if pd.isna(text) or not str(text).strip():
32
+ continue
33
+
34
+ meta = {
35
+ 'doc': (
36
+ row.get('doc').strip()
37
+ if pd.notna(row.get('doc')) and str(row.get('doc')).strip()
38
+ else os.path.splitext(os.path.basename(filepath))[0]
39
+ ),
40
+ 'titre': row.get('titre', ''),
41
+ 'chapitre': row.get('chapitre', ''),
42
+ 'article': row.get('article', ''),
43
+ 'sous_titre1': row.get('sous_titre1', ''),
44
+ 'sous_titre2': row.get('sous_titre2', ''),
45
+ 'sous_titre3': row.get('sous_titre3', ''),
46
+ 'pages': ", ".join(map(str, parse_pages(row.get('pages', '')))) # <- stringify to avoid Chroma error
47
+ }
48
+
49
+ documents.append(Document(page_content=str(text).strip(), metadata=meta))
50
+
51
+ print(f"πŸ“„ Loaded: {os.path.basename(filepath)} β†’ {len(documents)} documents")
52
+ return documents
53
+
54
+ def process_files():
55
+ all_docs = []
56
+ for file in os.listdir(DATA_DIR):
57
+ if file.endswith(".csv"):
58
+ full_path = os.path.join(DATA_DIR, file)
59
+ all_docs.extend(load_csv(full_path))
60
+
61
+ print(f"πŸ“Š Total: {len(all_docs)} full documents")
62
+
63
+ embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
64
+ db = Chroma.from_documents(all_docs, embedding=embeddings, persist_directory=PERSIST_DIR)
65
+ db.persist()
66
+
67
+ print(f"βœ… Done! Stored in {PERSIST_DIR}")
68
+
69
+ if __name__ == "__main__":
70
+ process_files()
app/services/__init__.py ADDED
File without changes
app/services/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (170 Bytes). View file
 
app/services/__pycache__/auth_utils.cpython-313.pyc ADDED
Binary file (662 Bytes). View file
 
app/services/__pycache__/chatbot.cpython-313.pyc ADDED
Binary file (1.56 kB). View file
 
app/services/auth_utils.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from app import bcrypt
2
+
3
+ def hash_password(password):
4
+ return bcrypt.generate_password_hash(password).decode('utf-8')
5
+
6
+ def check_password(hashed, password):
7
+ return bcrypt.check_password_hash(hashed, password)
app/services/chatbot.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/services/chatbot.py
2
+ from langchain_chroma import Chroma
3
+ from langchain_huggingface import HuggingFaceEmbeddings
4
+ from app.config import Config
5
+
6
+ PERSIST_DIR = Config.PERSIST_DIR
7
+ EMBEDDING_MODEL = Config.EMBEDDING_MODEL
8
+
9
+ embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
10
+ db = Chroma(persist_directory=PERSIST_DIR, embedding_function=embeddings)
11
+
12
+ def ask(query, k=6):
13
+ results = db.similarity_search_with_score(query, k=k)
14
+ response = []
15
+ for doc, score in results:
16
+ meta = doc.metadata
17
+ response.append({
18
+ "titre": meta.get("titre", ""),
19
+ "chapitre": meta.get("chapitre", ""),
20
+ "article": meta.get("article", ""),
21
+ "contenu": doc.page_content,
22
+ "doc": meta.get("doc", ""),
23
+ "pages": meta.get("pages", []) if isinstance(meta.get("pages"), list) else [meta.get("pages")] if meta.get("pages") else []
24
+ })
25
+ return response