We have this code base, most of it works perfectly, the back end receives and processes information correctly, what we are missing is the following:

When on autonomous actions, the reply for "[INFO] Final assistant reply:" is correctly received by the backend, but it is not being displayed on the webpage right now. While the results of autonomous function calling are correctly displayed on the webpage, the final response of these on autonomous behavior is not being displayed. Please fix this, implement what is necessary for us to achieve this. 


# app.py

import os
import json
from flask import Flask, request, jsonify, render_template
from openai import OpenAI
from functions import sleep_action, write_poem, take_note, move, ponder_about, associations, process_result
from memory import MemoryManager
from datetime import datetime
import logging
import threading
import time
from typing import Dict, Any
from dotenv import load_dotenv  # Import dotenv
from flask import Flask, request, jsonify, render_template, Response
from flask_cors import CORS
import queue


autonomous_actions_queue = queue.Queue()



# Load environment variables from .env file
load_dotenv()

app = Flask(__name__)
memory_manager = MemoryManager()

# -------------------------------
# Logging Configuration
# -------------------------------
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logger = logging.getLogger(__name__)

# File handler for logging to a file
file_handler = logging.FileHandler("logs/db_log_archive.log")
file_handler.setLevel(logging.INFO)  # Only log INFO level and above

# Formatter for the file handler
file_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(file_formatter)

# Add the file handler to the logger
logger.addHandler(file_handler)

# -------------------------------
# OpenAI Client Initialization
# -------------------------------
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
    logger.error("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")
    raise ValueError("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")

client = OpenAI(api_key=openai_api_key)

# -------------------------------
# Available Rooms and Actions
# -------------------------------
available_rooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden']
available_actions = ['move', 'sleep', 'write_poem', 'take_note', 'ponder_about', 'associations', 'process_result']

# Define function schemas
function_definitions = [
    {
        "type": "function",
        "function": {
            "name": "sleep",
            "description": "Sleep for a specified number of seconds.",
            "parameters": {
                "type": "object",
                "properties": {
                    "duration": {
                        "type": "integer",
                        "description": "Number of seconds to sleep."
                    }
                },
                "required": ["duration"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "process_result",
            "description": "Generate an archive for an important idea, or objective process, result or any important output and save it for safe archival.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project_data": {
                        "type": "string",
                        "description": "Prompt with extensive information to generate an archival or progress and results with. Use all the information necesary for a complete archival of information."
                    }
                },
                "required": ["project_data"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "associations",
            "description": "Generate subconscious associative ideas based on the concepts specified.",
            "parameters": {
                "type": "object",
                "properties": {
                    "concepts": {
                        "type": "string",
                        "description": "Concepts to generate associations from."
                    }
                },
                "required": ["concepts"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "write_poem",
            "description": "Write a simple poem.",
            "parameters": {
                "type": "object",
                "properties": {
                    "topic": {
                        "type": "string",
                        "description": "Topic of the poem with all the context, inspiration and ideas for it."
                    }
                },
                "required": ["topic"],
                "additionalProperties": False            
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "take_note",
            "description": "Take an extensive note for yourself, create a chain of thoughts style ideas, based on your previous memories, current objective, progress and results, use it as a general notepad to sculpt language as you please or need.",
            "parameters": {
                "type": "object",
                "properties": {
                    "content": {
                        "type": "string",
                        "description": "The content of the note."
                    }
                },
                "required": ["content"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "ponder_about",
            "description": "Make a chain of thought style response based on your previous memories",
            "parameters": {
                "type": "object",
                "properties": {
                    "idea": {
                        "type": "string",
                        "description": "The root idea for the chain of thought"
                    }
                },
                "required": ["idea"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "move",
            "description": "Move to a different room within the house, exact options: 'kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden'",
            "parameters": {
                "type": "object",
                "properties": {
                    "to_room": {
                        "type": "string",
                        "description": "The target room to move to."
                    }
                },
                "required": ["to_room"],
                "additionalProperties": False
            }
        }
    }
]

# -------------------------------
# Function Map
# -------------------------------
# Map function names to actual function implementations
function_map = {
    "sleep": sleep_action,
    "write_poem": write_poem,
    "take_note": take_note,
    "move": move,
    "ponder_about": ponder_about,
    "associations": associations,
    "process_result": process_result
}

# -------------------------------
# Centralized Function Execution
# -------------------------------

def execute_function(function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
    """
    Executes the specified function and integrates its output into memory.
    Returns the function's output.
    """
    if function_name not in function_map:
        raise ValueError(f"Function '{function_name}' not recognized.")

    func = function_map[function_name]
    func_output = func(**arguments)

    # Integrate into memory
    memory_manager.integrate_action(func_output)

    # Enqueue the action for real-time frontend updates if it's autonomous
    autonomous_actions_queue.put({
        "type": func_output["action_type"],  # Flattening the structure
        "content": func_output["content"]
    })

    # Logging
    logger.info(f"Executed function '{function_name}' with arguments {arguments}. Output: {func_output}")

    return {
        "type": func_output["action_type"],  # Ensure type is at the top level
        "content": func_output["content"]
    }

# Routes
# -------------------------------
@app.route("/")
def index():
    return render_template("index.html")

@app.route("/api/message", methods=["POST"])
def handle_message():
    user_message = request.json.get("message")
    if not user_message:
        logger.warning("No message provided in the request.")
        return jsonify({"reply": "No message provided.", "actions": []}), 400

    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    memory = memory_manager.get_memory()

    # Record user message in memory
    memory_manager.add_exchange("user", user_message)

    # Prepare messages for OpenAI
    messages = [
        {
            "role": "system",
            "content": (
                f"You are an autonomous AI agent living in a digital house. You are free to behave as you please unless the user sets a behavior, objective, or question for you. "
                f"Verbalize in natural language what you want to do and why next based on your possible actions and recent and past actions and thoughts and expressed desires or objectives until they are completed. "
                f"Only take direct action with tool use if instructed by the user to do something, if not, only verbalize your next actions."
                f" Current time is {current_time}. Memory: {memory}"
            )
        },
        {"role": "user", "content": user_message}
    ]
    logger.info(f"User Input: {user_message}")

    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",  # Ensure this model name is correct
            messages=messages,
            tools=function_definitions,  # Ensure you add your function definitions here
            tool_choice="auto",
            temperature=1.1,

        )
    except Exception as e:
        logger.error(f"Error during OpenAI API call: {str(e)}")
        return jsonify({"reply": "An error occurred while processing your request.", "actions": []}), 500

    choice = response.choices[0]
    message = choice.message

    actions = []

    if message.tool_calls:
        first_tool_call = message.tool_calls[0]
        function_name = first_tool_call.function.name
        arguments = json.loads(first_tool_call.function.arguments)

        logger.info(f"Autonomous action requested: {function_name} with arguments {arguments}")

        try:
            func_output = execute_function(function_name, arguments)
            actions.append({
                "type": function_name,
                "content": func_output
            })
        except ValueError as ve:
            logger.warning(str(ve))
            memory_manager.add_exchange("assistant", str(ve))
            return jsonify({"reply": str(ve), "actions": []}), 400
        except Exception as e:
            logger.error(f"Error executing function '{function_name}': {str(e)}")
            return jsonify({"reply": f"Error executing function '{function_name}': {str(e)}", "actions": []}), 500

        # Prepare the function response to send back to OpenAI
        function_response = {
            "role": "function",
            "name": function_name,
            "content": json.dumps(func_output)
        }

        # Append function response to messages
        exchange_content = f"Called function {function_name} with arguments {arguments}. Output: {func_output}"
        memory_manager.add_exchange("assistant", exchange_content)

        # Get the final assistant reply
        try:
            final_response = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages + [function_response]
            )
            final_reply = final_response.choices[0].message.content

            logger.info(f"Final assistant reply: {final_reply}")
        except Exception as e:
            logger.error(f"Error during final OpenAI API call: {str(e)}")
            return jsonify({"reply": "An error occurred while generating the reply.", "actions": []}), 500

        # Record assistant reply in memory
        memory_manager.add_exchange("assistant", final_reply)

        # Prepare the response with actions
        response_json = {
            "reply": final_reply,
            "actions": actions
        }

        return jsonify(response_json)
    else:
        # Direct response from the model without function call
        assistant_reply = message.content if message.content else ""
        logger.info(f"Assistant reply without function call: {assistant_reply}")
        memory_manager.add_exchange("assistant", assistant_reply)
        return jsonify({"reply": assistant_reply, "actions": []})  # No actions


# -------------------------------
# SSE Endpoint for Autonomous Actions
# -------------------------------
@app.route('/stream')
def stream():
    def event_stream():
        while True:
            try:
                action = autonomous_actions_queue.get()
                yield f'data: {json.dumps(action)}\n\n'
            except GeneratorExit:
                break
            except Exception as e:
                logger.error(f"Error in SSE stream: {e}")
                break

    return Response(event_stream(), mimetype='text/event-stream')


@app.route("/api/memory", methods=["GET"])
def get_memory():
    """
    Endpoint to retrieve the current memory, especially the current room.
    """
    memory = memory_manager.get_memory()
    # Extract current_room from memory
    # Assuming the last relevant line contains the current room
    lines = memory.strip().split('\n')
    current_room_line = next((line for line in lines if line.startswith("Current Location:")), None)
    current_room = "bedroom"  # Default room
    if current_room_line:
        current_room = current_room_line.replace("Current Location:", "").strip().lower()
    return jsonify({"current_room": current_room})

# -------------------------------
# Autonomous Decision-Making
# -------------------------------
def autonomous_decision():
    """
    Periodically prompts the LLM to choose the next action based on current memory and world state.
    Runs in a background thread.
    """
    while True:
        time.sleep(120)  # Wait for 120 seconds

        # Prepare the prompt with current memory and world state
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        memory = memory_manager.get_memory()
        world_state = f"Current Location: {memory_manager.current_room.capitalize()}"

        prompt_messages = [
            {
                "role": "system",
                "content": (
                    "You are an autonomous AI agent with a thought processing system, integrated with different models simulating a brain's functions. "
                    "You have access to tools for creative pondering, associative ideation, and other cognitive functions. "
                    "Your actions and interactions are logged in your memory, which includes past exchanges with the user and notes you've taken. "
                    f"Current time is {current_time}. Memory: {memory}\nWorld state: {world_state}\nAvailable actions: {', '.join(available_actions)}.\n\n"
                    "Please choose your next action from the available actions, ensuring that you consider your past interactions, notes, and current state and objective."
                )
            },
            {"role": "user", "content": "Choose your next action from the available actions and provide the necessary parameters."}
        ]

        try:
            response = client.chat.completions.create(
                model="gpt-4o-mini",  # Ensure this model name is correct
                messages=prompt_messages,
                tools=function_definitions,  # Ensure you add your function definitions here
                tool_choice="required",
            )
        except Exception as e:
            logger.error(f"Error during autonomous OpenAI API call: {str(e)}")
            print(f"Error during autonomous OpenAI API call: {str(e)}")  # Optional
            continue  # Skip this cycle on error

        choice = response.choices[0]
        message = choice.message
        memory_manager.add_exchange("AI Agent", message.content)

        logger.info(f"Autonomous decision message: {message}")

        if message.tool_calls:
            first_tool_call = message.tool_calls[0]
            function_name = first_tool_call.function.name
            arguments = json.loads(first_tool_call.function.arguments)

            logger.info(f"Autonomous action requested: {function_name} with arguments {arguments}")

            try:
                func_output = execute_function(function_name, arguments)
            except ValueError as ve:
                logger.warning(str(ve))
                memory_manager.add_exchange("assistant", str(ve))
                continue
            except Exception as e:
                logger.error(f"Error executing autonomous function '{function_name}': {str(e)}")
                continue  # Skip this action on error

            # Prepare the function response to send back to OpenAI
            function_response = {
                "role": "function",
                "name": function_name,
                "content": json.dumps(func_output)
            }

            # Append function response to messages
            exchange_content = f"Autonomous action: Called function {function_name} with arguments {arguments}. Output: {func_output}"
            memory_manager.add_exchange("assistant", exchange_content)

            # Get the final assistant reply
            try:
                final_response = client.chat.completions.create(
                    model="gpt-4o-mini",
                    messages=[
                        {
                            "role": "system",
                            "content": (
                                "You are an autonomous AI agent designed to have a thought processing system, integrated with different models simulating functions of a brain. "
                                "You are in a digital house. Consider your previous actions and interactions logged in your memory, which includes past exchanges with the user and notes you've taken. "
                                f"Current time is {current_time}. Memory: {memory}\nWorld state: {world_state}\n"
                                "Please create a final condensed response considering the most recent actions, objectives, interactions, and results, ensuring that you consider your most recent objectives and verbalizations, consider notes, current state, and use the following response given to the recent action to heavily inspire your output, no matter how absurd or short it is, it is your internal mental processing and representative of you:"
                            )
                        },
                        {"role": "user", "content": f"response: {function_response}"}
                    ],
                    temperature=1.1

                )
                final_reply = final_response.choices[0].message.content

                # Handle specific action confirmations if needed
                if func_output.get("action_type") == "move":
                    to_room = func_output["details"].get("to_room", memory_manager.current_room)
                    confirmation_message = f"Moved to {to_room.replace('_', ' ')}."
                    final_reply = confirmation_message + "\n" + final_reply

                logger.info(f"Autonomous assistant reply: {final_reply}")
            except Exception as e:
                logger.error(f"Error during final autonomous OpenAI API call: {str(e)}")
                continue  # Skip on error

            # Record autonomous assistant reply in memory
            memory_manager.add_exchange("assistant", final_reply)

            if func_output.get("action_type"):
                logger.info(f"Autonomous Action: {func_output['action_type']} with details {func_output['details']}")

        else:
            # Handle cases where the model didn't choose a function call
            assistant_reply = message.content if message.content else ""
            logger.info(f"Autonomous assistant reply without function call: {assistant_reply}")
            memory_manager.add_exchange("assistant", assistant_reply)

# -------------------------------
# Start Autonomous Decision Thread
# -------------------------------
autonomous_thread = threading.Thread(target=autonomous_decision, daemon=True)
autonomous_thread.start()

# -------------------------------
# Run the Flask App
# -------------------------------
if __name__ == "__main__":
    # Disable debug mode for production to prevent multiple threads
    app.run(debug=False)



# functions.py

from pydantic import BaseModel
from typing import Any, Dict
import logging
from dotenv import load_dotenv
from openai import OpenAI
import time
import json
import os

load_dotenv()

# Initialize OpenAI client
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
    raise ValueError("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")

client = OpenAI(api_key=openai_api_key)

# Setup logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Add a FileHandler to save INFO level logs to a file
file_handler = logging.FileHandler("logs/db_log_archive.log")
file_handler.setLevel(logging.INFO)

# Formatter for the file handler
file_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(file_formatter)

# Add the file handler to the logger
logger.addHandler(file_handler)

# Define Pydantic models for structured outputs

class FunctionOutput(BaseModel):
    action_type: str
    content: Dict[str, Any]
    notes: str = ""

def process_result(project_data: str) -> Dict[str, Any]:
    """
    Generates final results of autonomous behavior.
    """
    try:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",
           messages=[
                {"role": "system", "content": """
                 
                 
                 Serve as the final processor for an AI-driven project by combining multiple data points and generating a coherent, polished result.

Gather different data points from an AI process that has been working towards a defined objective. Depending on the objective, produce a cohesive final output (e.g., code, report, design) that reflects the goal of the entire project accurately and effectively. 

# Steps

1. **Understand the Objective**: Accurately parse the different data points, understanding the core goal of the project, and the role of each data point.
2. **Data Integration**: Integrate the data points into a unified solution, ensuring that the result aligns with the initial objective.
   - If data points complement each other, logically combine them.
   - If data points seem to be contradictory, determine the most suitable representation or recommendation based on context and relevance.
3. **Finalize the Result Based on Project Type**:
   - If the project is **code**, structure and finalize the code.
   - If the project is a **report**, create a comprehensive, well-organized report.
   - For different objectives, apply an appropriate medium for output (e.g., a **diagram**, **story**, or **analysis**).

# Output Format

- The output should be a cohesive final version, choosing the format accordingly:
  - **Code**: A complete program or module formatted with clear comments as needed.
  - **Report**: A structured multi-section report with introduction, body, and conclusion, each section logically building on each other.
  - **Design or Other Type**: Specific format depending on the nature of the data (e.g., diagrams should have titles, logical flow, etc.)
  
- **Length** and **Detail** should match the scope originally provided in the data points, ensuring nothing is overlooked, and all relevant information is included. Do not add any new information beyond what is given.

# Notes

- Ensure the final output is cohesive and logically connected.
- Ensure data points do not contradict, or if they do, provide reasoning to resolve.
- Provide explanations or rationales where necessary to ensure the user understands the reasons behind certain choices or compositions."""
                 
                 
                 },
                {"role": "user", "content": project_data}
            ]
        )

        projectResult = completion.choices[0].message.content
        logger.info(f"Generated process_result for '{project_data}': '{projectResult}'")
    except Exception as e:
        projectResult = f"An error occurred while generating the associations: {str(e)}"
        logger.error(projectResult)
    
    return FunctionOutput(
        action_type="process_result",
        content={"projectResult": projectResult},
        notes=""
    ).dict()

def associations(concepts: str, num_responses: int = 3) -> Dict[str, Any]:
    """
    Generates multiple associative thoughts based on the given concepts using OpenAI's API.
    """
    try:
        associations_list = []
        for _ in range(num_responses):
            completion = client.chat.completions.create(
                model="ft:gpt-4o-mini-2024-07-18:personal:new-asso-bot:ANTudXTo",
                messages=[
                    {"role": "system", "content": "You are a memory, emotional, and associative text generator. Generate text heavily charged with emotions, recollections, and associations based on the user input."},
                    {"role": "user", "content": concepts}
                ],
                temperature=1.1
            )
            response = completion.choices[0].message.content
            associations_list.append(response)
        
        associationsOUT = "\n\n".join(associations_list)  # Combine responses with a separator
        logger.info(f"Generated associations for '{concepts}': '{associationsOUT}'")
    except Exception as e:
        associationsOUT = f"An error occurred while generating the associations: {str(e)}"
        logger.error(associationsOUT)
    
    return FunctionOutput(
        action_type="associations",
        content={"associations": associationsOUT},
        notes=""
    ).dict()

def ponder_about(idea: str, num_responses: int = 3) -> Dict[str, Any]:
    """
    Generates multiple chains of thought based on the given idea using OpenAI's API.
    """
    try:
        thoughts_list = []
        for _ in range(num_responses):
            completion = client.chat.completions.create(
                model="ft:gpt-4o-mini-2024-07-18:personal:bizarro-bot:AL1jyXBE",
                messages=[
                    {"role": "system", "content": "You are a bot that randomly generates creative, absurd, philosophical, bizarre, profound, and unique phrases on demand."},
                    {"role": "user", "content": idea}
                ],
                temperature=1.1
            )
            response = completion.choices[0].message.content
            thoughts_list.append(response)
        
        CoT = "\n\n".join(thoughts_list)  # Combine responses with a separator
        logger.info(f"Generated pondering for '{idea}': '{CoT}'")
    except Exception as e:
        CoT = f"An error occurred while generating the pondering: {str(e)}"
        logger.error(CoT)
    
    return FunctionOutput(
        action_type="ponder_about",
        content={"CoT": CoT},
        notes=""
    ).dict()

def sleep_action(duration: int) -> Dict[str, Any]:
    """
    Simulates sleeping for a given duration.
    """
    try:
        time.sleep(duration)
        logger.info(f"Slept for {duration} seconds.")
    except Exception as e:
        logger.error(f"Error during sleep: {str(e)}")
    return FunctionOutput(
        action_type="sleep",
        content={"duration": duration},
        notes=f"Slept for {duration} seconds."
    ).dict()

def write_poem(topic: str, num_poems: int = 3) -> Dict[str, Any]:
    """
    Generates multiple poems based on the given topic using OpenAI's API.
    """
    try:
        poems_list = []
        for _ in range(num_poems):
            completion = client.chat.completions.create(
                model="ft:gpt-4o-mini-2024-07-18:personal:absurdist-mind:AKnbG6M0",
                messages=[
                    {"role": "system", "content": "You are Jupiter, a creative bot that generates creative and unique phrases on demand. Write a poem based on the following topic:"},
                    {"role": "user", "content": topic}
                ]
            )
            response = completion.choices[0].message.content
            poems_list.append(response)
        
        poem = "\n\n".join(poems_list)  # Combine poems with a separator
        logger.info(f"Generated poems for '{topic}': '{poem}'")
    except Exception as e:
        poem = f"An error occurred while generating the poems: {str(e)}"
        logger.error(poem)
    
    return FunctionOutput(
        action_type="write_poem",
        content={"poem": poem},
        notes=""
    ).dict()

def take_note(content: str) -> Dict[str, Any]:
    """
    Takes a note with the given content.
    """
    logger.info(f"Taking note: {content}")
    return FunctionOutput(
        action_type="take_note",
        content={"note": content},
        notes=f"Note taken: {content}"
    ).dict()

def move(to_room: str) -> Dict[str, Any]:
    """
    Moves the character to a specified room and returns a related description.
    """
    available_rooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden']
    
    room_descriptions = {
        'kitchen': "You step into the kitchen, where the smell of freshly brewed coffee fills the air. The countertops are cluttered with ingredients for a meal.",
        'living_room': "The living room is cozy and inviting, with soft lighting and a large sofa. The TV is on, playing an old movie.",
        'bedroom': "The bedroom is peaceful, with soft sheets and a calm ambiance. The window is slightly open, letting in a cool breeze.",
        'study': "In the study, shelves of books line the walls. A desk sits in the center, papers scattered across it, as if someone was deep in thought.",
        'library': "The library is quiet, filled with towering shelves of books. The air smells of old paper, and the dim light creates a calm atmosphere.",
        'garden': "The garden is alive with vibrant colors and the soft hum of insects. A stone path winds through blooming flowers, leading to a wooden bench under a sprawling oak tree. The air is fragrant with the scent of jasmine and fresh earth, and you can hear the faint trickle of a small fountain nearby."
    }
    
    if to_room not in available_rooms:
        logger.warning(f"Attempted to move to invalid room: '{to_room}'")
        return FunctionOutput(
            action_type="error",
            content={"message": f"Room '{to_room}' does not exist."},
            notes=""
        ).dict()
    
    room_text = room_descriptions.get(to_room, "You are in an indescribable place.")
    logger.info(f"Moved to '{to_room}': {room_text}")
    
    return FunctionOutput(
        action_type="move",
        content={"to_room": to_room},
        notes=room_text
    ).dict()


# memory.py

import json
from collections import deque
from datetime import datetime, timedelta
import os
import logging
from typing import Any, Dict
from dotenv import load_dotenv
from openai import OpenAI


# Load environment variables from .env file
load_dotenv()

logger = logging.getLogger(__name__)

class MemoryManager:
    def __init__(self, notes_db_path="notes/notes_db.json", results_db_path="results/results_db.json"):
        self.recent_actions = deque(maxlen=15)       # Stores the 15 most recent actions
        self.short_term_memory = deque(maxlen=30)   # Stores the 30 most recent actions
        self.long_term_memory = []                  # Stores consolidated memories
        self.exchanges = deque(maxlen=30)           # Stores the last 30 user-AI exchanges
        self.total_time_spent = timedelta()
        self.last_consolidation = datetime.now()
        self.current_room = "bedroom"               # Initialize starting room
        self.notes_db_path = notes_db_path
        self.results_db_path = results_db_path

        ## Databases Initialization

        # Initialize notes database if it doesn't exist
        if not os.path.isfile(self.notes_db_path):
            os.makedirs(os.path.dirname(self.notes_db_path), exist_ok=True)
            with open(self.notes_db_path, 'w') as f:
                json.dump({"notes": []}, f, indent=4)
            logger.info(f"Notes database created at {self.notes_db_path}")
            print(f"Notes database created at {self.notes_db_path}")  # Optional

        # Initialize results database if it doesn't exist
        if not os.path.isfile(self.results_db_path):
            os.makedirs(os.path.dirname(self.results_db_path), exist_ok=True)
            with open(self.results_db_path, 'w') as f:
                json.dump({"results": []}, f, indent=4)
            logger.info(f"Results database created at {self.results_db_path}")
            print(f"Results database created at {self.results_db_path}")  # Optional

    def add_exchange(self, role: str, content: str):
        """
        Adds a user or assistant exchange to the exchanges deque.
        Compresses exchanges into a summary when the limit is reached.
        """
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        exchange_entry = {
            "timestamp": timestamp,
            "role": role,
            "content": content
        }
        self.exchanges.append(exchange_entry)
        self.recent_actions.append(f"[{timestamp}] {role.capitalize()}: {content}")
        self.short_term_memory.append(f"[{timestamp}] {role.capitalize()}: {content}")

        if len(self.exchanges) == self.exchanges.maxlen:
            self.compress_exchanges()

    def integrate_action(self, action_output: Dict[str, Any]):
        """
        Integrates the standardized function output into the memory system.
        """
        action_type = action_output.get("action_type")
        content = action_output.get("content", {})
        notes = action_output.get("notes", "")

        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # Add to exchanges
        exchange_content = f"Performed action '{action_type}' with content {content}."
        self.add_exchange("assistant", exchange_content)

        # Handle specific action types
        if action_type == "move":
            to_room = content.get("to_room", self.current_room)
            action_description = f"Moved to {to_room.replace('_', ' ')}."
            self.add_action(action_description)
            self.current_room = to_room  # Update current room
        elif action_type == "write_poem":
            poem = content.get("poem", "")
            action_description = f"Poem ideas: {poem}"
            self.add_action(action_description)
        elif action_type == "take_note":
            note = content.get("note", "")
            action_description = f"Took a note: {note}"
            self.add_action(action_description)
            self.save_note(note)
        elif action_type == "associations":
            associations_out = content.get("associationsOUT", "")
            action_description = f"Internal AI process generated associations: {associations_out}"
            self.add_action(action_description)
        elif action_type == "process_result":
            project_results = content.get("projectResult", "")
            action_description = f"Generated project results: {project_results}"
            self.add_action(action_description)
            self.save_final_result(project_results)
        elif action_type == "ponder_about":
            cot = content.get("CoT", "")
            action_description = f"Internal AI pondered: {cot}"
            self.add_action(action_description)
        elif action_type == "sleep":
            duration = content.get("duration", 0)
            action_description = f"Slept for {duration} seconds."
            self.add_action(action_description)
            self.sleep_action(duration)
        elif action_type == "error":
            error_message = content.get("message", "An unknown error occurred.")
            action_description = f"Error: {error_message}"
            self.add_action(action_description)
        # Add more action types as needed

        # Handle notes if any
        if notes:
            self.save_note(notes)

    def compress_exchanges(self):
        """
        Compresses the last 20 exchanges into a summary and appends it to long-term memory.
        """
        exchanges_list = list(self.exchanges)
        summary = self.generate_summary(exchanges_list, summary_type="exchanges")
        consolidated_summary = {
            "type": "exchanges_summary",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "summary": summary
        }
        self.long_term_memory.append(consolidated_summary)
        logger.info("Compressed the last 20 exchanges into a summary.")
        print(f"Compressed Exchanges Summary: {summary}")  # Optional: Print summary

        # Clear the exchanges deque after compression
        self.exchanges.clear()

    def generate_summary(self, data: Any, summary_type: str = "general") -> str:
        """
        Generates a summary using the OpenAI API from the provided data.
        Ensures the summary fits the existing data structure.

        Parameters:
            data (list): List of exchange or action entries to summarize.
            summary_type (str): Type of summary ('exchanges' or 'actions').

        Returns:
            str: The generated summary.
        """
        if summary_type == "exchanges":
            prompt_description = "Summarize the following autonomous assistant and user exchanges."
            formatted_data = "\n".join([f"{ex['role'].capitalize()}: {ex['content']}" for ex in data])
        elif summary_type == "actions":
            prompt_description = "Summarize the following autonomous agent recent actions."
            formatted_data = "\n".join(data)
        else:
            prompt_description = "Summarize the following data."
            formatted_data = "\n".join(data)

        prompt = f"{prompt_description}\n\n{formatted_data}\n\nSummary:"

        try:
            # Initialize OpenAI client if not already done
            openai_api_key = os.getenv("OPENAI_API_KEY")
            if not openai_api_key:
                logger.error("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")
                raise ValueError("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")

            completion = OpenAI(api_key=openai_api_key).chat.completions.create(
                model="gpt-4o-mini",  # Ensure this model name is correct
                messages=[
                    {"role": "system", "content": "You are a memory system of an Autonomous AI agent. Summarize the provided data into comprehensive paragraphs with descriptions of actions taken, ideas, objectives, progress, and next steps."},
                    {"role": "user", "content": prompt}
                ],
                max_tokens=300,
                temperature=0.5,
            )

            summary = completion.choices[0].message.content
            logger.info(f"Generated summary: {summary}")
            return summary
        except Exception as e:
            logger.error(f"Failed to generate summary: {e}")
            return "Summary could not be generated."

    def add_action(self, action: str):
        """
        Adds an action to recent actions and short-term memory.
        """
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        action_entry = f"[{timestamp}] {action}"
        self.recent_actions.append(action_entry)
        self.short_term_memory.append(action_entry)
        logger.info(f"Action added: {action_entry}")
        print(f"Action added: {action_entry}")  # Optional: Print action

        if len(self.short_term_memory) == self.short_term_memory.maxlen:
            self.compress_short_term_memory()

    def compress_short_term_memory(self):
        """
        Compresses the short-term memory into a summary and appends it to long-term memory.
        """
        short_term_list = list(self.short_term_memory)
        summary = self.generate_summary(short_term_list, summary_type="actions")
        consolidated_summary = {
            "type": "actions_summary",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "summary": summary
        }
        self.long_term_memory.append(consolidated_summary)
        logger.info("Compressed short-term memory into a summary.")
        print(f"Compressed Short-Term Memory Summary: {summary}")  # Optional: Print summary

        # Clear the short-term memory deque after compression
        self.short_term_memory.clear()

    def sleep_action(self, duration_seconds: int):
        """
        Handles the sleep action by updating time spent and memory.
        """
        self.total_time_spent += timedelta(seconds=duration_seconds)
        summary = f"Slept for {duration_seconds} seconds."
        self.add_action(summary)

    def move_action(self, to_room: str):
        """
        Handles the move action by updating the current room and memory.
        """
        from_room = self.current_room
        self.current_room = to_room
        action = f"Moved from {from_room} to {to_room}."
        self.add_action(action)

    def take_note_action(self, content: str):
        """
        Handles taking a note by adding it to memory and saving to the JSON database.
        """
        action = f"Took a note: {content}"
        self.add_action(action)
        self.save_note(content)

    def save_note(self, content: str):
        """
        Saves a note to the JSON database.
        """
        try:
            with open(self.notes_db_path, 'r+') as f:
                data = json.load(f)
                data['notes'].append({
                    "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "content": content
                })
                f.seek(0)
                json.dump(data, f, indent=4)
            logger.info(f"Note saved: {content}")
            print(f"Note saved: {content}")  # Optional: Print note saving
        except Exception as e:
            logger.error(f"Failed to save note: {e}")
            print(f"Failed to save note: {e}")  # Optional: Print error

    def save_final_result(self, f_result: str):
        """
        Saves a final results into the JSON database.
        """
        try:
            with open(self.results_db_path, 'r+') as f:
                data = json.load(f)
                data['results'].append({
                    "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "content": f_result
                })
                f.seek(0)
                json.dump(data, f, indent=4)
            logger.info(f"Final results saved: {f_result}")
            print(f"Final results saved: {f_result}")  # Optional: Print note saving
        except Exception as e:
            logger.error(f"Failed to save final results: {e}")
            print(f"Failed to save final results: {e}")  # Optional: Print error

    def consolidate_memories(self):
        """
        Consolidates short-term memory into long-term memory periodically.
        """
        now = datetime.now()
        if now - self.last_consolidation > timedelta(hours=24):  # Consolidate every 24 hours
            if self.short_term_memory:
                summary = self.generate_summary(list(self.short_term_memory), summary_type="actions")
                consolidated_summary = {
                    "type": "daily_consolidation",
                    "timestamp": now.strftime("%Y-%m-%d %H:%M:%S"),
                    "summary": summary
                }
                self.long_term_memory.append(consolidated_summary)
                self.short_term_memory.clear()
                self.last_consolidation = now
                self.add_action("Memories have been consolidated into long-term memory.")
                logger.info("Short-term memory consolidated into long-term memory.")
                print("Short-term memory consolidated into long-term memory.")  # Optional: Print consolidation

    def get_memory(self) -> str:
        """
        Returns a string representation of the memory including long-term and short-term memories,
        exchanges summary, and recent actions.
        """
        self.consolidate_memories()
        memory = ""
        if self.long_term_memory:
            memory += "Long-Term Memory:\n"
            for entry in self.long_term_memory:
                memory += f"[{entry['timestamp']}] ({entry['type']}) {entry['summary']}\n"
            memory += "\n"
        memory += "Short-Term Memory:\n"
        memory += "\n".join(self.short_term_memory)
        memory += "\n\n"
        if self.exchanges:
            memory += "Active Exchanges:\n"
            memory += "\n".join([f"[{ex['timestamp']}] {ex['role'].capitalize()}: {ex['content']}" for ex in self.exchanges])
        memory += f"\n\nCurrent Location: {self.current_room.capitalize()}"
        return memory


// static/script.js

// Function to escape HTML special characters
function escapeHtml(text) {
    return text
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;")
        .replace(/\n/g, "<br>");  // Replace newline with <br> for proper multiline display

}

// Event listeners for sending messages
document.getElementById('send').addEventListener('click', sendMessage);
document.getElementById('input').addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
        sendMessage();
    }
});

/**
 * Sends the user's message to the backend and handles the response.
 */
function sendMessage() {
    const input = document.getElementById('input');
    const message = input.value.trim();
    if (message === "") return;

    appendMessage("User", message, 'user');
    input.value = "";

    fetch('/api/message', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: message })
    })
    .then(response => response.json())
    .then(data => {
        if (data.reply) {
            appendMessage("Assistant", data.reply, 'assistant');
            handleAssistantActions(data.actions);
        }
    })
    .catch(error => {
        //appendMessage("Error", "Failed to send the message.", 'error');
        console.error('Error:', error);
    });
}

/**
 * Appends a message to the chat container.
 * @param {string} sender - The sender of the message ('User', 'Assistant', etc.).
 * @param {string} text - The message text.
 * @param {string} type - The type of message for styling purposes.
 */

function appendMessage(sender, text, type = 'user') {
    const chat = document.getElementById('chat');

    // Handle long texts by splitting them
    const maxLength = 500; // Adjust as needed
    const chunks = text.match(new RegExp(`.{1,${maxLength}}`, 'g'));

    chunks.forEach((chunk, index) => {
        const msg = document.createElement('div');

        if (type === 'assistant-action') {
            msg.classList.add('assistant-action');
        }
        if (type === 'error') {
            msg.classList.add('error');
        }

        // Escape HTML for each chunk
        const escapedText = escapeHtml(chunk);
        
        // Only add the sender once, without "(cont.)"
        if (index === 0) {
            msg.innerHTML = `<strong>${sender}:</strong> ${escapedText}`;
        } else {
            msg.innerHTML = `${escapedText}`;  // No sender on continuation chunks
        }

        chat.appendChild(msg);
    });

    chat.scrollTop = chat.scrollHeight;
}

/**
 * Handles the assistant's actions by processing structured action data.
 * @param {Array} actions - List of action objects.
 */
function handleAssistantActions(actions) {
    actions.forEach(action => {
        switch(action.type) {
            case 'move':
                moveCharacter(action.content.to_room);
                appendMessage("Assistant", `Moved to ${capitalizeRoom(action.content.to_room)}.`, 'assistant-action');
                break;
            case 'write_poem':
                displayPoem(action.content.poem);
                break;
            case 'take_note':
                displayNote(action.content.note);
                break;
            case 'sleep':
                appendMessage("Assistant", `Slept for ${action.content.duration} seconds.`, 'assistant-action');
                break;
            case 'process_result':
                appendMessage("Assistant", `Processed result: ${action.content.projectResult}`, 'assistant-action');
                break;
            case 'associations':
                appendMessage("Assistant", `Generated associations: ${action.content.associations}`, 'assistant-action');
                break;
            case 'ponder_about':
                appendMessage("Assistant", `Pondered about: ${action.content.CoT}`, 'assistant-action');
                break;
            case 'error':
                appendMessage("Assistant", `Error: ${action.content.message}`, 'error');
                break;
            // Add more cases for other action types as needed
            default:
                console.warn(`Unknown action type: ${action.type}`);
        }
    });
}

/**
 * Appends autonomous actions received via SSE to the chat.
 * @param {Object} action - The action object received from SSE.
 */
function appendAutonomousAction(action) {
    switch(action.type) {
        case 'move':
            moveCharacter(action.content.to_room);
            appendMessage("Assistant", `Moved to ${capitalizeRoom(action.content.to_room)}.`, 'assistant-action');
            break;
        case 'write_poem':
            displayPoem(action.content.poem);
            break;
        case 'take_note':
            displayNote(action.content.note);
            break;
        case 'sleep':
            appendMessage("Assistant", `Slept for ${action.content.duration} seconds.`, 'assistant-action');
            break;
        case 'process_result':
            appendMessage("Assistant", `Processed result: ${action.content.projectResult}`, 'assistant-action');
            break;
        case 'associations':
            appendMessage("Assistant", `Generated associations: ${action.content.associations}`, 'assistant-action');
            break;
        case 'ponder_about':
            appendMessage("Assistant", `Pondered about: ${action.content.CoT}`, 'assistant-action');
            break;
        case 'error':
            appendMessage("Assistant", `Error: ${action.content.message}`, 'error');
            break;
        // Add more cases for other action types as needed
        default:
            console.warn(`Unknown autonomous action type: ${action.type}`);
    }
}

/**
 * Handles the assistant's response by processing any actions.
 * @param {Object} data - The complete response data from the backend.
 */
function handleAssistantResponse(data) {
    const actions = data.actions || {};

    // Process each action accordingly
    for (const [action, value] of Object.entries(actions)) {
        switch(action) {
            case 'move':
                moveCharacter(value);
                appendMessage("Assistant", `Moved to ${capitalizeRoom(value)}.`, 'assistant-action');
                break;
            // Add cases for other actions as needed
            default:
                console.warn(`Unknown action: ${action}`);
        }
    }

    // Additionally, you can handle content-based actions
    // displayContentBasedOnReply(data.reply);
}

/**
 * Connects to the SSE stream to receive autonomous actions.
 */
function connectSSE() {
    const eventSource = new EventSource('/stream');

    eventSource.onmessage = function(event) {
        const action = JSON.parse(event.data);
        appendAutonomousAction(action);
    };

    eventSource.onerror = function(err) {
        console.error('SSE connection error:', err);
        eventSource.close();
    };
}

/**
 * Moves the character to the specified room by manipulating the DOM.
 * @param {string} room - The target room's ID.
 */
function moveCharacter(room) {
    console.log('Moving character to:', room);
    room = room.toLowerCase();

    const availableRooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden'];
    if (!availableRooms.includes(room)) {
        console.error(`Unknown room: ${room}`);
        return;
    }

    const character = document.getElementById('character');
    if (!character) {
        console.error('Character element not found');
        return;
    }

    // Check if character is already in the target room to avoid unnecessary DOM manipulation
    if (character.parentElement && character.parentElement.id === room) {
        console.log('Character is already in', room);
        return; // Character is already in the correct room
    }

    // First remove character from current room
    if (character.parentElement) {
        character.parentElement.removeChild(character);
    }

    // Reset all room borders
    availableRooms.forEach(r => {
        const roomDiv = document.getElementById(r);
        if (roomDiv) {
            roomDiv.style.borderColor = '#000';
        }
    });

    const targetRoom = document.getElementById(room);
    if (targetRoom) {
        // Ensure the character is visible
        character.style.display = 'block';

        // Add character to new room
        targetRoom.appendChild(character);

        // Highlight the new room
        targetRoom.style.borderColor = 'green';

        // Add a small animation effect
        character.style.opacity = '0';
        character.offsetHeight; // Force reflow
        character.style.opacity = '1';

        console.log(`Moved character to ${room}`);
    }
}

/**
 * Displays a poem in the chat container.
 * @param {string} poemText - The poem text.
 */
function displayPoem(poemText) {
    const chat = document.getElementById('chat');
    const poemDiv = document.createElement('div');
    poemDiv.classList.add('assistant-action'); // Differentiate autonomous ways
    poemDiv.innerHTML = `<strong>Poem:</strong><pre>${escapeHtml(poemText)}</pre>`;
    chat.appendChild(poemDiv);
    chat.scrollTop = chat.scrollHeight;
}

/**
 * Displays a note in the chat container.
 * @param {string} noteText - The note text.
 */
function displayNote(noteText) {
    const chat = document.getElementById('chat');
    const noteDiv = document.createElement('div');
    noteDiv.classList.add('assistant-action'); // Differentiate autonomous ways
    noteDiv.innerHTML = `<strong>Note:</strong> ${escapeHtml(noteText)}`;
    chat.appendChild(noteDiv);
    chat.scrollTop = chat.scrollHeight;
}

/**
 * Capitalizes the room name for display purposes.
 * @param {string} room - The room name in lowercase with underscores.
 * @returns {string} - The capitalized room name.
 */
function capitalizeRoom(room) {
    return room.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase());
}

/**
 * Initializes the character's position when the page loads.
 */
function initializeCharacter() {
    const character = document.getElementById('character');
    if (character) {
        // Make sure character is visible initially
        character.style.display = 'block';
        character.style.opacity = '1';
        console.log('Character initialized in bedroom');
    }
}

/**
 * Initializes the SSE connection when the page loads.
 */
function initializeSSE() {
    connectSSE();
}

// Call these when the page loads
document.addEventListener('DOMContentLoaded', () => {
    initializeCharacter();
    initializeSSE();
});






<!-- index.html -->

<!DOCTYPE html>
<html>
<head>
    <title>LLM Controlled Environment</title>
    <style>
        body { 
            font-family: 'Arial', sans-serif; 
            background-color: #2C2A28; /* Dark, cozy brown */
            color: #E0DCC9; /* Warm beige for contrast */
            margin: 0; 
            padding: 20px; 
        }

        h1 { 
            text-align: center; 
            color: #EEDAC6; /* Light warm brown */
            margin-bottom: 20px; 
        }

        #chat-container {
            width: 80%;
            margin: 0 auto;
            padding: 10px;
            background-color: #3A3836; /* Slightly lighter brown for contrast */
            border-radius: 10px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
        }

        #chat { 
            border: 1px solid #44403C; 
            padding: 10px; 
            height: 400px; 
            overflow-y: scroll; 
            background-color: #2E2C2A; /* Darker brown for chat area */
            border-radius: 5px;
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2);
        }

        #input { 
            width: 80%; 
            padding: 10px; 
            border: 1px solid #44403C; 
            border-radius: 5px;
            margin-top: 10px;
            background-color: #3A3836; 
            color: #EEDAC6; 
        }

        #send { 
            padding: 10px 20px; 
            border: none; 
            background-color: #725D4B; /* Warm brown for buttons */
            color: #fff; 
            border-radius: 5px; 
            cursor: pointer;
            margin-left: 10px;
            transition: background-color 0.3s ease;
        }

        #send:hover {
            background-color: #8E7358; /* Lighter, warmer brown on hover */
        }

        #house { 
            margin-top: 20px; 
            display: flex; 
            justify-content: space-around; 
            flex-wrap: wrap;
            background-color: #3A3836;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
        }

        .room {
            display: inline-block;
            width: 200px;
            height: 200px;
            border: 2px solid #725D4B; 
            margin: 10px;
            position: relative;
            background-color: #2E2C2A; /* Dark brown for rooms */
            border-radius: 10px;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
            transition: border-color 0.5s ease, transform 0.3s ease;
        }

        .room:hover {
            border-color: #8E7358;
            transform: translateY(-5px);
        }

        .room span { 
            position: absolute; 
            top: 10px; 
            left: 10px; 
            font-weight: bold; 
            font-size: 16px;
            color: #E0DCC9; /* Warm beige text */
        }

        .character {
            width: 30px;
            height: 30px;
            background-color: #C9625E; /* Cozy reddish-brown for character */
            border-radius: 50%;
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            transition: all 0.5s ease;
            box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
        }

        .character:hover {
            transform: translateX(-50%) scale(1.1);
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.7);
        }

        .assistant-action { 
            background-color: #3A3836; 
            padding: 5px; 
            border-radius: 5px; 
            margin: 5px 0;
            color: #E0DCC9;
        }

        .error { 
            color: #C9625E; 
            font-weight: bold;
        }

        pre {
            white-space: pre-wrap;
            color: #E0DCC9;
            background-color: #3A3836;
            padding: 10px;
            border-radius: 5px;
            overflow-x: auto;
        }
    </style>
</head>
<body>
    <h1>LLM Controlled Environment</h1>
    <div id="chat-container">
        <div id="chat"></div>
        <input type="text" id="input" placeholder="Enter your message here..." autofocus>
        <button id="send">Send</button>
    </div>

    <div id="house">
        <div class="room" id="kitchen">
            <span>Kitchen</span>
        </div>
        <div class="room" id="living_room">
            <span>Living Room</span>
        </div>
        <div class="room" id="bedroom">
            <span>Bedroom</span>
            <!-- Character starts in the bedroom -->
            <div class="character" id="character"></div>
        </div>
        <div class="room" id="study">
            <span>Study</span>
        </div>
        <div class="room" id="library">
            <span>Library</span>
        </div>
        <div class="room" id="garden">
            <span>Garden</span>
        </div>
        <!-- Add more rooms as needed -->
    </div>

    <script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>