so i'll now explain what all i need to do
i need to create a visual study buddy or a companion which helps people study
it understands your emotion in real time and helps with it
so firstly, it contains your to do list, you add your todo list and it knows wht you are studying, it also has a q n a geenertor which leverages a rag model as per user's input, input can be a website, it can be. pdf ot even text, ok next 
now as you have your todo list as what you are studying so it keeps the current task as a tickbox on the top, now main so it reads your emotion continuously and if you are happy it pops a message that helps you be more energetic and if sad it sorts you 
so this is my whole idea 
now help me with the code
for developmeent i was thinking of uing kaggle
and when the codes are finalised will use my local 
so help me with it please
with complete code
use llamaindex, langgraph,langchain, and streamlit importantly restuse whatever you want to
also it will use live camera to see the live emotions of the person and as per his emotions drop a message, please give complete code
also i am using mac and i don't have open ai api, use ollama 
this is my project idea for that i have made the following cdode

# logger.py

import logging
import os
from logging.handlers import RotatingFileHandler

# Create a logger
logger = logging.getLogger("study_buddy_logger")
logger.setLevel(logging.DEBUG)  # Capture all levels of logs

# Create a logs directory if it doesn't exist
if not os.path.exists("logs"):
    os.makedirs("logs")

# Create a file handler that logs debug and higher level messages
file_handler = RotatingFileHandler("logs/study_buddy.log", maxBytes=5*1024*1024, backupCount=2)
file_handler.setLevel(logging.DEBUG)

# Create console handler with a higher log level
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# Create a logging format
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# Add the handlers to the logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# database_mongodb.py

import pymongo
from pymongo import MongoClient
from datetime import datetime, timedelta
from logger import logger

class EmotionDatabase:
    def __init__(self, uri="mongodb://localhost:27017/", db_name="study_buddy", collection_name="emotions"):
        try:
            self.client = MongoClient(uri)
            self.db = self.client[db_name]
            self.collection = self.db[collection_name]
            logger.info("Connected to MongoDB successfully.")
        except Exception as e:
            logger.error(f"Failed to connect to MongoDB: {e}")
            self.client = None
            self.db = None
            self.collection = None

    def insert_emotion(self, emotion):
        """
        Insert a detected emotion with a timestamp into the MongoDB collection.
        
        :param emotion: Detected emotion as a string.
        """
        if self.collection:
            try:
                emotion_record = {
                    "timestamp": datetime.utcnow(),
                    "emotion": emotion
                }
                self.collection.insert_one(emotion_record)
                logger.debug(f"Inserted emotion '{emotion}' into MongoDB.")
            except Exception as e:
                logger.error(f"Error inserting emotion into MongoDB: {e}")
        else:
            logger.error("MongoDB collection is not initialized.")

    def get_recent_emotions(self, minutes=5):
        """
        Retrieve emotions detected in the last 'minutes' minutes.
        
        :param minutes: Time window in minutes.
        :return: List of emotions.
        """
        if self.collection:
            try:
                time_threshold = datetime.utcnow() - timedelta(minutes=minutes)
                emotions_cursor = self.collection.find({"timestamp": {"$gte": time_threshold}})
                emotions = [doc['emotion'] for doc in emotions_cursor]
                logger.debug(f"Retrieved {len(emotions)} emotions from the last {minutes} minutes.")
                return emotions
            except Exception as e:
                logger.error(f"Error fetching recent emotions from MongoDB: {e}")
                return []
        else:
            logger.error("MongoDB collection is not initialized.")
            return []

    def close_connection(self):
        """
        Close the MongoDB connection.
        """
        if self.client:
            self.client.close()
            logger.debug("Closed MongoDB connection.")

# database_mongodb.py

import pymongo
from pymongo import MongoClient
from datetime import datetime, timedelta
from logger import logger

class EmotionDatabase:
    def __init__(self, uri="mongodb://localhost:27017/", db_name="study_buddy", collection_name="emotions"):
        try:
            self.client = MongoClient(uri)
            self.db = self.client[db_name]
            self.collection = self.db[collection_name]
            logger.info("Connected to MongoDB successfully.")
        except Exception as e:
            logger.error(f"Failed to connect to MongoDB: {e}")
            self.client = None
            self.db = None
            self.collection = None

    def insert_emotion(self, emotion):
        """
        Insert a detected emotion with a timestamp into the MongoDB collection.
        
        :param emotion: Detected emotion as a string.
        """
        if self.collection:
            try:
                emotion_record = {
                    "timestamp": datetime.utcnow(),
                    "emotion": emotion
                }
                self.collection.insert_one(emotion_record)
                logger.debug(f"Inserted emotion '{emotion}' into MongoDB.")
            except Exception as e:
                logger.error(f"Error inserting emotion into MongoDB: {e}")
        else:
            logger.error("MongoDB collection is not initialized.")

    def get_recent_emotions(self, minutes=5):
        """
        Retrieve emotions detected in the last 'minutes' minutes.
        
        :param minutes: Time window in minutes.
        :return: List of emotions.
        """
        if self.collection:
            try:
                time_threshold = datetime.utcnow() - timedelta(minutes=minutes)
                emotions_cursor = self.collection.find({"timestamp": {"$gte": time_threshold}})
                emotions = [doc['emotion'] for doc in emotions_cursor]
                logger.debug(f"Retrieved {len(emotions)} emotions from the last {minutes} minutes.")
                return emotions
            except Exception as e:
                logger.error(f"Error fetching recent emotions from MongoDB: {e}")
                return []
        else:
            logger.error("MongoDB collection is not initialized.")
            return []

    def close_connection(self):
        """
        Close the MongoDB connection.
        """
        if self.client:
            self.client.close()
            logger.debug("Closed MongoDB connection.")

# components/todo.py

import pandas as pd
import streamlit as st
from logger import logger

class ToDo:
    def __init__(self):
        logger.debug("Initializing ToDo component.")
        if 'todo' not in st.session_state:
            st.session_state.todo = pd.DataFrame(columns=['Task', 'Status'])
            logger.info("Created new to-do list in session state.")
        else:
            logger.info("Loaded existing to-do list from session state.")
    
    def add_task(self, task: str):
        if task:
            if task in st.session_state.todo['Task'].values:
                logger.warning(f"Attempted to add duplicate task: {task}")
                st.warning(f"Task '{task}' already exists.")
                return
            new_task_df = pd.DataFrame([{'Task': task, 'Status': 'Pending'}])
            st.session_state.todo = pd.concat([st.session_state.todo, new_task_df], ignore_index=True)
            logger.info(f"Added new task: {task}")
            st.success(f"Added task: {task}")
        else:
            logger.warning("Attempted to add empty task.")
            st.warning("Task cannot be empty.")
    
    def toggle_task(self, index: int):
        try:
            current_status = st.session_state.todo.at[index, 'Status']
            new_status = 'Completed' if current_status == 'Pending' else 'Pending'
            st.session_state.todo.at[index, 'Status'] = new_status
            logger.info(f"Toggled task at index {index} to {new_status}.")
        except Exception as e:
            logger.error(f"Error toggling task at index {index}: {e}")
            st.error("An error occurred while toggling the task status.")
    
    def display_tasks(self):
        logger.debug("Displaying tasks.")
        st.subheader("Your Tasks")
        if not st.session_state.todo.empty:
            for index, row in st.session_state.todo.iterrows():
                checkbox = st.checkbox(row['Task'], value=(row['Status'] == 'Completed'), key=f"task_{index}")
                if checkbox != (row['Status'] == 'Completed'):
                    self.toggle_task(index)
        else:
            st.info("No tasks added yet.")
            logger.info("To-do list is currently empty.")

# components/emotion.py

import streamlit as st
from fer import FER
from PIL import Image
import numpy as np
from streamlit_webrtc import (
    WebRtcMode,
    webrtc_streamer,
    VideoTransformerBase,
)
from database_mongodb import EmotionDatabase
from logger import logger
from threading import Lock
from datetime import datetime, timedelta

class EmotionDetector:
    def __init__(self):
        logger.debug("Initializing EmotionDetector component.")
        self.db = EmotionDatabase()
        self.lock = Lock()
        if 'detector' not in st.session_state:
            st.session_state.detector = FER()
            logger.info("Initialized and cached FER detector in session state.")
        else:
            logger.info("Loaded existing FER detector from session state.")
        
        if 'emotion' not in st.session_state:
            st.session_state.emotion = 'Neutral'
            logger.info("Set initial emotion to Neutral in session state.")
        else:
            logger.info(f"Loaded existing emotion '{st.session_state.emotion}' from session state.")
    
    def display_emotion(self):
        st.subheader("🎥 Real-Time Emotion Detection")
        
        class EmotionTransformer(VideoTransformerBase):
            def __init__(self, emotion_detector):
                self.emotion_detector = emotion_detector
                self.db = emotion_detector.db
                self.lock = emotion_detector.lock
                self.emotion = 'Neutral'
            
            def transform(self, frame):
                img = frame.to_image()
                logger.debug("Transforming video frame for emotion detection.")
                emotion = self.emotion_detector.detect_emotion(img)
                with self.lock:
                    self.emotion = emotion
                    self.db.insert_emotion(emotion)
                # Annotate the image with detected emotion
                annotated_img = self.emotion_detector.annotate_image(img, emotion)
                return annotated_img
                    
        webrtc_ctx = webrtc_streamer(
            key="emotion-detection",
            mode=WebRtcMode.SENDRECV,
            video_transformer_factory=lambda: EmotionTransformer(self),
            media_stream_constraints={"video": True, "audio": False},
            async_processing=True,
        )
        
        # Analyze emotions periodically to determine persistent mood
        # Using Streamlit's session_state to track emotions
        with st.spinner("Analyzing emotions..."):
            emotion = st.session_state.emotion
            st.write(f"**Current Emotion:** {emotion}")
            self.evaluate_emotion(emotion)
    
    def detect_emotion(self, image: Image.Image) -> str:
        """
        Detect emotion from the given image.
        
        :param image: PIL Image.
        :return: Detected emotion as a string.
        """
        logger.debug("Resizing image for faster emotion detection.")
        image = image.resize((320, 240))  # Reduce resolution for speed
        img_array = np.array(image.convert('RGB'))
        emotions = st.session_state.detector.top_emotion(img_array)
        if emotions:
            emotion, score = emotions
            logger.info(f"Detected emotion: {emotion} with score {score:.2f}.")
            return emotion.capitalize()
        logger.info("No emotion detected; defaulting to Neutral.")
        return 'Neutral'
    
    def annotate_image(self, image: Image.Image, emotion: str) -> Image.Image:
        """
        Optionally annotate the image with the detected emotion.
        
        :param image: PIL Image.
        :param emotion: Detected emotion.
        :return: Annotated PIL Image.
        """
        from PIL import ImageDraw, ImageFont
        
        draw = ImageDraw.Draw(image)
        try:
            font = ImageFont.truetype("arial.ttf", 24)
        except IOError:
            font = ImageFont.load_default()
        
        text = f"Emotion: {emotion}"
        draw.text((10, 10), text, fill="red", font=font)
        return image
    
    def evaluate_emotion(self, current_emotion: str):
        """
        Evaluate the current emotion compared to recent emotions and display messages accordingly.
        
        :param current_emotion: The latest detected emotion.
        """
        # Define the time window for analysis (e.g., last 5 minutes)
        time_window_minutes = 5
        recent_emotions = self.db.get_recent_emotions(minutes=time_window_minutes)
        emotion_counts = {}
        for emotion in recent_emotions:
            emotion_counts[emotion] = emotion_counts.get(emotion, 0) + 1
        
        logger.debug(f"Emotion counts in the last {time_window_minutes} minutes: {emotion_counts}")
        
        if not recent_emotions:
            st.info("No recent emotions detected.")
            logger.info("No recent emotions to analyze.")
            return
        
        # Determine the most frequent emotion
        most_frequent_emotion = max(emotion_counts, key=emotion_counts.get)
        frequency = emotion_counts[most_frequent_emotion]
        total = len(recent_emotions)
        percentage = (frequency / total) * 100
        
        logger.debug(f"Most frequent emotion: {most_frequent_emotion} ({percentage:.2f}%)")
        
        # Set a threshold for considering an emotion as persistent
        persistence_threshold = 70  # Percent
        
        if percentage > persistence_threshold:
            if most_frequent_emotion in ['Happy', 'Surprise']:
                st.success("You're feeling great! Keep up the positive vibes! 😊")
                logger.info("Displayed Happy/Surprise feedback.")
            elif most_frequent_emotion in ['Sad', 'Angry']:
                st.warning("It seems you're feeling down. Consider taking a short break or talking to someone. 😢")
                logger.info("Displayed Sad/Angry feedback.")
            else:
                st.info("You're in a neutral mood. Keep focused and continue your study session! 📖")
                logger.info("Displayed Neutral/Other feedback.")
        else:
            st.info("Your mood varies. Stay consistent and take care of yourself! 🌟")
            logger.info("Displayed Varying mood feedback.")

# components/llm.py

import requests
import streamlit as st
from langchain.llms.base import LLM
from typing import Optional
from logger import logger

class OllamaLLM(LLM):
    """
    A custom LangChain LLM wrapper for Ollama's local API.
    """
    @property
    def _llm_type(self):
        return "ollama"
    
    def __init__(self, model_name: str = "llama2", host: str = "http://localhost:11434"):
        """
        Initialize the OllamaLLM with the specified model and host.
        
        :param model_name: Name of the Ollama model to use.
        :param host: Host URL where Ollama's API is running.
        """
        self.model_name = model_name
        self.host = host.rstrip('/')
        logger.debug(f"Initialized OllamaLLM with model '{self.model_name}' at host '{self.host}'.")
    
    def _call(self, prompt: str, stop: Optional[list] = None) -> str:
        """
        Send the prompt to Ollama's API and retrieve the response.
        
        :param prompt: The prompt string to send to the model.
        :param stop: Optional list of stop sequences.
        :return: The response string from the model.
        """
        url = f"{self.host}/v1/engines/{self.model_name}/completions"
        headers = {
            "Content-Type": "application/json"
        }
        data = {
            "prompt": prompt,
            "max_tokens": 256,  # Reduced from 512 for speed
            "temperature": 0.5,  # Reduced from 0.7 for more deterministic outputs
            "top_p": 0.9,
            "stop": stop
        }

        logger.debug(f"Sending prompt to Ollama: {prompt}")
        try:
            response = requests.post(url, headers=headers, json=data)
            response.raise_for_status()
            result = response.json()
            completion = result.get("completion", "").strip()
            logger.info(f"Received response from Ollama: {completion}")
            return completion
        except requests.exceptions.RequestException as e:
            logger.error(f"Error communicating with Ollama API: {e}")
            st.error("Error communicating with Ollama API.")
            return "I'm sorry, I couldn't process that."

# components/study_material.py

import streamlit as st
import requests
from bs4 import BeautifulSoup
from llama_index import SimpleVectorIndex, SimpleDirectoryReader
import PyPDF2
import io
import os
import tempfile
from logger import logger

class StudyMaterialProcessor:
    def __init__(self):
        logger.debug("Initializing StudyMaterialProcessor component.")
    
    def process_input(self, input_type: str, input_data) -> SimpleVectorIndex:
        """
        Process user input (Text, URL, PDF) and create an index.
        
        :param input_type: Type of input ('Text', 'URL', 'PDF').
        :param input_data: The actual input data.
        :return: An instance of SimpleVectorIndex.
        """
        logger.debug(f"Processing input of type: {input_type}.")
        documents = []

        if input_type == "Text":
            logger.debug("Processing Text input.")
            documents = [input_data]
        elif input_type == "URL":
            logger.debug(f"Processing URL input: {input_type}.")
            try:
                response = requests.get(input_type, timeout=10)
                response.raise_for_status()
                soup = BeautifulSoup(response.text, 'html.parser')
                text = soup.get_text(separator='\n')
                documents.append(text)
                logger.info(f"Fetched and extracted text from URL: {input_type}.")
            except Exception as e:
                logger.error(f"Error fetching URL '{input_type}': {e}")
                st.error(f"Error fetching URL: {e}")
        elif input_type == "PDF":
            logger.debug("Processing PDF input.")
            try:
                pdf_reader = PyPDF2.PdfReader(io.BytesIO(input_data.read()))
                for page in pdf_reader.pages:
                    text = page.extract_text()
                    if text:
                        documents.append(text)
                logger.info("Extracted text from PDF.")
            except Exception as e:
                logger.error(f"Error reading PDF: {e}")
                st.error(f"Error reading PDF: {e}")
        else:
            logger.warning(f"Unsupported input type: {input_type}.")
            st.error("Unsupported input type.")

        if documents:
            try:
                with st.spinner("Creating index. Please wait..."):
                    with tempfile.TemporaryDirectory() as tmpdirname:
                        file_path = os.path.join(tmpdirname, "documents.txt")
                        with open(file_path, 'w', encoding='utf-8') as f:
                            for doc in documents:
                                f.write(doc + "\n")
                        logger.debug(f"Written documents to temporary file: {file_path}.")
                        reader = SimpleDirectoryReader(tmpdirname)
                        index = SimpleVectorIndex(reader.load_data())
                        logger.info("Index created successfully.")
                        return index
            except Exception as e:
                logger.error(f"Error creating index: {e}")
                st.error(f"Error creating index: {e}")
                return None
        else:
            logger.warning("No valid documents to index.")
            st.error("No valid documents to index.")
            return None

# components/qa_generator.py

import streamlit as st
from llama_index import SimpleVectorIndex
from logger import logger

class QAGenerator:
    def __init__(self):
        logger.debug("Initializing QAGenerator component.")
    
    def create_response(self, index: SimpleVectorIndex, query: str) -> str:
        """
        Query the index with the user's question and get the response from LLM.
        
        :param index: The SimpleVectorIndex instance.
        :param query: The user's question.
        :return: The answer string.
        """
        if not index:
            st.error("No index available. Please add and process study materials first.")
            logger.error("Attempted to generate a response without a valid index.")
            return ""
        try:
            logger.debug(f"Querying index with question: {query}")
            response = index.query(query, llm=st.session_state.llm)
            answer = response.response
            logger.info(f"Generated answer for query '{query}': {answer}")
            return answer
        except Exception as e:
            logger.error(f"Error generating response for query '{query}': {e}")
            st.error("An error occurred while generating the response.")
            return "I'm sorry, I couldn't generate a response."

# app.py

import streamlit as st
from components.todo import ToDo
from components.emotion import EmotionDetector
from components.llm import OllamaLLM
from components.study_material import StudyMaterialProcessor
from components.qa_generator import QAGenerator
from logger import logger

@st.cache_resource
def load_llm():
    logger.debug("Loading OllamaLLM and caching the instance.")
    return OllamaLLM(model_name="llama2", host="http://localhost:11434")

def main():
    logger.debug("Starting the Visual Study Buddy application.")
    st.set_page_config(page_title="📚 Visual Study Buddy", layout="wide")
    st.title("📚 Visual Study Buddy")

    # Initialize Components
    todolist = ToDo()
    logger.debug("Initialized ToDo component.")
    emotion_detector = EmotionDetector()
    logger.debug("Initialized EmotionDetector component.")
    processor = StudyMaterialProcessor()
    logger.debug("Initialized StudyMaterialProcessor component.")
    qa_generator = QAGenerator()
    logger.debug("Initialized QAGenerator component.")

    # Initialize LLM and store in session_state
    if 'llm' not in st.session_state:
        st.session_state.llm = load_llm()
        logger.info("Loaded and cached OllamaLLM.")
    else:
        logger.debug("Loaded existing OllamaLLM from session_state.")

    # Sidebar for To-Do List
    st.sidebar.header("📝 To-Do List")
    with st.sidebar.form("add_task_form"):
        new_task = st.text_input("Add a new task")
        submit_task = st.form_submit_button("Add Task")
        if submit_task:
            logger.info(f"User submitted a new task: {new_task}")
            todolist.add_task(new_task)

    # Display To-Do List
    st.sidebar.subheader("Your Tasks")
    todolist.display_tasks()

    # Main Content Layout
    col1, col2 = st.columns(2)

    with col1:
        logger.debug("Rendering EmotionDetector in main content.")
        emotion_detector.display_emotion()
        
        st.markdown("---")
        st.header("📂 Add Study Materials")
        input_type = st.selectbox("Select input type:", ["Text", "URL", "PDF"], key="select_input_type")
        
        if input_type == "Text":
            user_text = st.text_area("Enter your study material here:", key="text_input")
            if st.button("Process Text", key="process_text"):
                logger.info("Processing Text input.")
                index = processor.process_input("Text", user_text)
                if index:
                    st.session_state.study_index = index
                    logger.info("Text input processed and index stored in session_state.")
                    st.success("Text processed and index created.")
        
        elif input_type == "URL":
            user_url = st.text_input("Enter the URL of the study material:", key="url_input")
            if st.button("Process URL", key="process_url"):
                logger.info(f"Processing URL input: {user_url}.")
                index = processor.process_input("URL", user_url)
                if index:
                    st.session_state.study_index = index
                    logger.info("URL input processed and index stored in session_state.")
                    st.success("URL content fetched and index created.")
        
        elif input_type == "PDF":
            uploaded_pdf = st.file_uploader("Upload a PDF file", type=["pdf"], key="pdf_uploader")
            if uploaded_pdf and st.button("Process PDF", key="process_pdf"):
                logger.info("Processing PDF input.")
                index = processor.process_input("PDF", uploaded_pdf)
                if index:
                    st.session_state.study_index = index
                    logger.info("PDF input processed and index stored in session_state.")
                    st.success("PDF processed and index created.")

    with col2:
        logger.debug("Rendering QAGenerator in main content.")
        st.header("❓ Q&A Generator")
        user_question = st.text_input("Ask a question related to your study material:", key="qa_input")
        if st.button("Get Answer", key="get_answer"):
            logger.info(f"User asked a question: {user_question}")
            if 'study_index' in st.session_state and st.session_state.study_index:
                answer = qa_generator.create_response(st.session_state.study_index, user_question)
                logger.info(f"Generated answer: {answer}")
                st.write(f"**Answer:** {answer}")
            else:
                logger.warning("User attempted to get an answer without processing study materials.")
                st.error("Please add and process study materials first.")

    # Display Current Task
    st.markdown("---")
    if not st.session_state.todo.empty:
        current_task = st.session_state.todo.iloc[0]['Task']
        st.subheader(f"📌 Current Task: {current_task}")
        logger.debug(f"Displaying current task: {current_task}")
    else:
        st.subheader("📌 No current task.")
        logger.debug("No current task to display.")

    # Display Emotion-based Message at the Top
    st.markdown("""---""")
    st.subheader(f"😊 Current Emotion: {st.session_state.emotion}")
    logger.debug(f"Displaying current emotion: {st.session_state.emotion}")

    # Footer
    st.markdown("""---""")
    st.markdown("Developed by [Your Name](https://github.com/yourgithub)")
    logger.debug("Rendered footer.")

if __name__ == "__main__":
    main()
    logger.debug("Visual Study Buddy application has terminated.")


what do you think about this?