We have this project, code base, right now it works, from the function calling system, to the memory system, but we need to revamp the whole memory system, making a modular way to integrate all outputs, from autonomous to interactions with the user to be passed directly into a single function to organize them into the memory system we have, but we need to effectively make the memory system a lot simpler and modular so we can easily add more functions that almost autonomically get added to the memory system. 

Right now almost each function has its own memory integration and we want to remove that. We want to remove the redundancy of memory system and logging, making sure we are not saving double information on the AI memory. 


#This Python code implements a web application using Flask to facilitate interactions with an autonomous AI agent 
# that operates in a simulated digital house environment. The AI agent can perform specific actions such as moving between rooms, 
# writing poems, taking notes, and generating associative ideas. It employs logging for error tracking, integrates with OpenAI's API 
# to process user inputs and manage its memory state, and handles requests via various endpoints for user messaging and memory retrieval, 
# all while enabling autonomous decision-making in the background.

import os
import json
from flask import Flask, request, jsonify, render_template
from openai import OpenAI
from functions import sleep, write_poem, take_note, move, ponder_about, associations
from memory import MemoryManager
from datetime import datetime
import logging
import threading
import time

app = Flask(__name__)
memory_manager = MemoryManager()

# Enhanced logging configuration with timestamps and log levels
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Get the logger
logger = logging.getLogger(__name__)

# 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)  # Only log INFO level and above

# Optionally, set a different format for the file logs if you want
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)

# Now, logger.info will go to both the console and "db_log_archive.log"

# Initialize OpenAI client with the new integration
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)

# Define available rooms
available_rooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden']

# Define available actions
available_actions = ['move', 'sleep', 'write_poem', 'take_note', 'ponder_about', 'associations']

# 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": "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
            }
        }
    }
]

# Map function names to actual functions
function_map = {
    "sleep": sleep,
    "write_poem": write_poem,
    "take_note": take_note,
    "move": move,
    "ponder_about": ponder_about,
    "associations": associations
}

@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."}), 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)

    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 behaiviour, objective or question for you. 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. Current time is {current_time}. Memory: {memory}"},
        {"role": "user", "content": user_message}
    ]

    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",  # Corrected model name
            messages=messages,
            tools=function_definitions,
            tool_choice="auto", 
        )
    except Exception as e:
        logger.error(f"Error during OpenAI API call: {str(e)}")
        return jsonify({"reply": "An error occurred while processing your request."}), 500

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

    memory_manager.add_exchange("AI Agent", message.content)

    logger.info(f"Received message from OpenAI: {message}")
    print(f"Received OpenAI Message: {message}")  # Optional

    actions = {}  # Dictionary to hold actions to send to the frontend

    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}")

        if function_name in function_map:
            func = function_map[function_name]
            try:
                func_output = func(**arguments)
            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)}"}), 500

            # Handle specific functions that affect memory and prepare actions
            if function_name == "sleep":
                memory_manager.sleep_action(func_output["duration"])
                logger.info(f"Executed sleep for {func_output['duration']} seconds.")
            elif function_name == "take_note":
                content = arguments.get("content", "")
                memory_manager.take_note_action(content)
                logger.info(f"Executed take_note with content: {content}")
            elif function_name == "write_poem":
                poem = func_output.get("poem", "")
                memory_manager.add_action("Wrote a poem.")
                logger.info("Executed write_poem.")
            elif function_name == "associations":
                associationsOUT = func_output.get("associationsOut", "")
                memory_manager.add_action(f"Association generated: {associationsOUT}")
                logger.info(f"Association generated: {associationsOUT}")
            elif function_name == "ponder_about":
                CoT = func_output.get("CoT", "")
                memory_manager.add_action(f"Pondering result: {CoT}")
                logger.info(f"Pondering result: {CoT}")
            elif function_name == "move":
                to_room = arguments.get("to_room", memory_manager.current_room)
                if to_room not in available_rooms:
                    logger.warning(f"Invalid target room: {to_room}")
                    return jsonify({"reply": f"Room '{to_room}' does not exist."}), 400
                memory_manager.move_action(to_room)
                logger.info(f"Executed move to {to_room}.")
                actions['move'] = to_room  # Add move action to actions dictionary

            # 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",  # Corrected model name
                    messages=messages + [function_response]
                )
                final_reply = final_response.choices[0].message.content

                logger.info(f"Final assistant reply: {final_reply}")
                print(f"Final Assistant Reply: {final_reply}")  # Optional
            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."}), 500

            # Record assistant reply in memory
            memory_manager.add_exchange("assistant", final_reply)
            print(f"Assistant Reply: {final_reply}")  # Optional

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

            return jsonify(response_json)
        else:
            logger.warning(f"Function '{function_name}' not recognized.")
            memory_manager.add_exchange("assistant", f"Function '{function_name}' not recognized.")
            return jsonify({"reply": f"Function '{function_name}' not recognized."}), 400
    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

@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})

def extract_move_command(text):
    """
    Extracts movement commands from the assistant's reply.
    Expected format: "Move to [Room]"
    """
    import re
    pattern = r"Move to ([A-Za-z\s_]+)"
    match = re.search(pattern, text, re.IGNORECASE)
    if match:
        room = match.group(1).strip().lower().replace(" ", "_")
        if room in available_rooms:
            return room
    return None

def autonomous_decision():
    """
    Periodically prompts the LLM to choose the next action based on current memory and world state.
    """
    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": f"You are an autonomous AI agent,use the tools at your disposal. Use notes to save important outputs, thoughts, ideas, progress, objectives or full results of projects, use it as a drawer for importat information or results that will forever be saved, anything that you don't put in your notes will be eventually lost. You are designed to have a thought processing system where you have the autonomous ability of function calling, you are an integration of different models simulating functions of a brain that verbalize yout next action. You are in a digital house. You are given different actions to process information in different ways, all these processes you will consider as being content generated by yourself. Your actions and interactions are logged in your memory, which includes past exchanges with the user and notes you've taken. Current time is {current_time}. Memory: {memory}\nWorld state: {world_state}\nAvailable actions: {', '.join(available_actions)}.\n\nPlease 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",  # Corrected model name
                messages=prompt_messages,
                tools=function_definitions,
                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}")
        # print(f"Autonomous Decision Message: {message}")  # Optional

        actions = {}  # Dictionary to hold actions to send to the frontend

        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}")

            if function_name in function_map:
                func = function_map[function_name]
                try:
                    func_output = func(**arguments)
                except Exception as e:
                    logger.error(f"Error executing autonomous function '{function_name}': {str(e)}")
                    continue  # Skip this action on error

                # Handle specific functions that affect memory and prepare actions
                if function_name == "sleep":
                    memory_manager.sleep_action(func_output["duration"])
                    logger.info(f"Autonomous executed sleep for {func_output['duration']} seconds.")
                elif function_name == "associations":
                    associationsOUT = func_output.get("associationsOut", "")
                    memory_manager.add_action(f"Association generated: {associationsOUT}")
                    logger.info(f"Association generated: {associationsOUT}")
                elif function_name == "ponder_about":
                    CoT = func_output.get("CoT", "")
                    memory_manager.add_action(f"Pondering result: {CoT}")
                    logger.info(f"Pondering result: {CoT}")
                elif function_name == "take_note":
                    content = func_output.get("note", "")
                    memory_manager.take_note_action(content)
                    logger.info(f"Autonomous executed take_note with content: {content}")
                elif function_name == "write_poem":
                    poem = func_output.get("poem", "")
                    memory_manager.add_action(f"Wrote poem: {poem} .")
                    logger.info("Autonomous executed write_poem.")
                elif function_name == "move":
                    to_room = arguments.get("to_room", memory_manager.current_room)
                    if to_room not in available_rooms:
                        logger.warning(f"Invalid target room: {to_room}")
                        continue  # Skip invalid move
                    memory_manager.move_action(to_room)
                    logger.info(f"Autonomous executed move to {to_room}.")
                    actions['move'] = to_room  # Add move action to actions dictionary

                # 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",  # Corrected model name
                        messages= [
                            {"role": "system", "content": f"You are an autonomous AI agent designed to have a thought processing system, you are the integration model of different models simulating functions of your own brain, from creative pondering and associative ideation. 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. 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}"}
                        ]
                    )
                    final_reply = final_response.choices[0].message.content

                    # Append movement confirmation if the function was 'move'
                    if function_name == "move":
                        confirmation_message = f"Moved to {to_room.replace('_', ' ')}."
                        final_reply = confirmation_message + "\n" + final_reply

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

                # Record autonomous assistant reply in memory
                memory_manager.add_exchange("assistant", final_reply)
                print(f"Autonomous Assistant Reply: {final_reply}")  # Optional

                # Prepare the response with actions (if needed)
                # Since this is a background thread, you might want to notify the frontend differently
                # For simplicity, we'll log the actions

                if actions:
                    logger.info(f"Autonomous Actions: {actions}")

                # Continue the loop without returning
                continue

            else:
                logger.warning(f"Function '{function_name}' not recognized in autonomous action.")
                continue
        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 the autonomous decision-making in a background thread
autonomous_thread = threading.Thread(target=autonomous_decision, daemon=True)
autonomous_thread.start()

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



#This Python code defines a module that utilizes the OpenAI API for generating creative outputs such as poems, associative thoughts, 
# and philosophical ponderings based on user input. It also incorporates enhanced logging capabilities to record activities and errors, 
# with structured outputs managed by Pydantic models. Additionally, it includes functions for simulating sleep and moving to different 
# predefined rooms with descriptions, logging relevant actions along the way.

from pydantic import BaseModel
from typing import Any, Dict
from openai import OpenAI
import time
import logging

# Enhanced logging configuration with timestamps and log levels
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Get the logger
logger = logging.getLogger(__name__)

# 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)  # Only log INFO level and above

# Optionally, set a different format for the file logs if you want
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)

# Now, logger.info will go to both the console and "db_log_archive.log"

client = OpenAI()

# Define Pydantic models for structured outputs

class SleepOutput(BaseModel):
    duration: int  # in seconds

class WritePoemOutput(BaseModel):
    poem: str

class NoteOutput(BaseModel):
    note: str

class MoveOutput(BaseModel):
    to_room: str
    room_text: str

class WriteThoughtsOutput(BaseModel):
    CoT: str

class WriteAssociationsOutput(BaseModel):
    associationsOut: str

# Define environment functions

def associations(concepts: str) -> Dict[str, Any]:
    """
    Generates associative thoughts based on the given concepts using OpenAI's API.
    """
    try:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",  
            messages=[
                {"role": "system", "content": "You are a memory, emotional, and associative text generator. Generate text heavily charged with emotions, recollections, and associations of different kinds based on the user input, no matter what it is."},
                {"role": "user", "content": concepts}  # Use the provided prompt
            ]
        )

        # Extract the content string from the response
        associationsOUT = completion.choices[0].message.content
        logger.info(f"Generated association for '{concepts}': '{associationsOUT}'")

    except Exception as e:
        associationsOUT = f"An error occurred while generating the associations: {str(e)}"
        logger.error(associationsOUT)
    
    return WriteAssociationsOutput(associationsOut=associationsOUT).dict()

def ponder_about(idea: str) -> Dict[str, Any]:
    """
    Generates a chain of thought based on the given idea using OpenAI's API.
    """
    try:
        completion = client.chat.completions.create(
            model="gpt-4o-mini", 
            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}  # Use the provided prompt
            ]
        )

        # Extract the content string from the response
        CoT = completion.choices[0].message.content
        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 WriteThoughtsOutput(CoT=CoT).dict()

def sleep(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 SleepOutput(duration=duration).dict()

def write_poem(topic: str) -> Dict[str, Any]:
    """
    Generates a poem based on the given topic using OpenAI's API.
    """
    try:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",  
            messages=[
                {"role": "system", "content": "You are Jupiter, a creative bot that generates creative and unique phrases on demand. Generate a poem based on the following prompt:"},
                {"role": "user", "content": topic}  # Use the provided prompt
            ]
        )

        # Get the poem from the response
        poem = completion.choices[0].message.content
        logger.info(f"Generated poem for '{topic}': '{poem}'")

    except Exception as e:
        poem = f"An error occurred while generating the poem: {str(e)}"
        logger.error(poem)
    
    return WritePoemOutput(poem=poem).dict()

def take_note(content: str) -> Dict[str, Any]:
    """
    Takes a note with the given content.
    """
    # Implement storing the note somewhere if needed
    logger.info(f"Taking note: {content}")
    return NoteOutput(note=content).dict()

def move(to_room: str) -> Dict[str, Any]:
    """
    Moves the character to a specified room and returns a related description.
    """
    # List of available rooms
    available_rooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden']

    # Dictionary mapping rooms to descriptions or related text
    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 in available_rooms:
        room_text = room_descriptions[to_room]
        logger.info(f"New room:  '{to_room}': '{room_text}'")
        return MoveOutput(to_room=to_room, room_text=room_text).dict()
    else:
        logger.warning(f"Attempted to move to invalid room: '{to_room}'")
        return MoveOutput(to_room=to_room, room_text="Room not found").dict()



#This Python code defines a `MemoryManager` class that manages a memory system for an autonomous AI. 
# It stores recent user-AI exchanges, actions, and notes, compresses these into summaries for long-term memory, 
# and maintains a notes database in JSON format. The manager includes functionalities for adding exchanges and actions, 
# consolidating memories, and generating summaries using the OpenAI API, along with handling specific actions like moving rooms 
# and taking notes.

import json
from collections import deque
from datetime import datetime, timedelta
import os
import logging
from openai import OpenAI

logger = logging.getLogger(__name__)
client = OpenAI()

class MemoryManager:
    def __init__(self, notes_db_path="notes/notes_db.json"):
        self.recent_actions = deque(maxlen=9)       # Stores the 9 most recent actions
        self.short_term_memory = deque(maxlen=15)   # Stores the 15 most recent actions
        self.long_term_memory = []                  # Stores consolidated memories
        self.exchanges = deque(maxlen=20)           # Stores the last 20 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

        # 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

    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 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, summary_type="general"):
        """
        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 autonomous agent following data."
            formatted_data = "\n".join(data)

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

        try:
            response = client.chat.completions.create(
                model="gpt-4o-mini",  # Corrected model name
                messages=[
                    {"role": "system", "content": "You are a memory system of an Autonomous AI system. Summarize the provided data into comprehensive paragraphs with descriptions of actions taken and ideas had. Describe crearly the recent actions, objectives and next steps and actions to take to progress Output paragraphs of comprehensive descriptions and then bullet points of the most important current situation, objectives, progress and following actions."},
                    {"role": "user", "content": prompt}
                ],
                max_tokens=550,
                temperature=0.8,
            )
            
            summary = response.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 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
    


<!DOCTYPE html>
<html>
<head>
    <title>LLM Controlled Environment</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #eef2f3; margin: 0; padding: 20px; }
        h1 { text-align: center; }
        #chat-container {
            width: 80%;
            margin: 0 auto;
        }
        #chat { 
            border: 1px solid #ccc; 
            padding: 10px; 
            height: 400px; 
            overflow-y: scroll; 
            background-color: #fafafa; 
            border-radius: 5px;
            box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
        }
        #input { 
            width: 80%; 
            padding: 10px; 
            border: 1px solid #ccc; 
            border-radius: 5px;
            margin-top: 10px;
        }
        #send { 
            padding: 10px 20px; 
            border: none; 
            background-color: #28a745; 
            color: #fff; 
            border-radius: 5px; 
            cursor: pointer;
            margin-left: 10px;
        }
        #send:hover {
            background-color: #218838;
        }
        #house { 
            margin-top: 20px; 
            display: flex; 
            justify-content: space-around; 
            flex-wrap: wrap;
        }
        .room {
            display: inline-block;
            width: 200px;
            height: 200px;
            border: 2px solid #000;
            margin: 10px;
            position: relative;  /* Keep this */
            background-color: #f0f0f0;
            border-radius: 10px;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
            transition: border-color 0.5s ease;
        }
        .room span { 
            position: absolute; 
            top: 10px; 
            left: 10px; 
            font-weight: bold; 
            font-size: 16px;
        }
        .character {
            width: 30px;
            height: 30px;
            background-color: red;
            border-radius: 50%;
            position: absolute;  /* Keep absolute positioning */
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);  /* Center horizontally */
            transition: all 0.5s ease;
            box-shadow: 0 0 5px rgba(0,0,0,0.5);
            z-index: 100;  /* Ensure character appears above other elements */
            display: block;  /* Ensure visibility */
        }
        .character:hover {
            transform: translateX(-50%) scale(1.1);
            box-shadow: 0 0 10px rgba(0,0,0,0.7);
        }
        .assistant-action { 
            background-color: #e2e3e5; 
            padding: 5px; 
            border-radius: 5px; 
            margin: 5px 0;
        }
        .error { 
            color: red; 
            font-weight: bold;
        }
    </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>


// 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');
            handleAssistantResponse(data);
        }
    })
    .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');
    const msg = document.createElement('div');
    
    if (type === 'assistant-action') {
        msg.classList.add('assistant-action');
    }
    if (type === 'error') {
        msg.classList.add('error');
    }

    msg.innerHTML = `<strong>${sender}:</strong> ${text}`;
    chat.appendChild(msg);
    chat.scrollTop = chat.scrollHeight;
}

/**
 * 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);
}

/**
 * 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 content like poems or notes based on the assistant's reply.
 * @param {string} reply - The assistant's reply.
 */
function displayContentBasedOnReply(reply) {
    // Example: Detect if the reply contains a poem
    const poemRegex = /Roses are red|I wandered lonely as a cloud/i; // Enhanced regex for better detection
    if (poemRegex.test(reply)) {
        displayPoem(reply);
    }

    // Example: Detect if the reply contains a note
    const noteRegex = /Note:/i;
    if (noteRegex.test(reply)) {
        displayNote(reply);
    }
}

/**
 * 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>${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> ${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());
}

// Poll the server every 1 second to get the latest memory and update character position
setInterval(fetchMemory, 1000);

/**
 * Fetches the current memory from the backend to determine the character's current room.
 */
function fetchMemory() {
    fetch('/api/memory', {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' }
    })
    .then(response => response.json())
    .then(data => {
        console.log('Fetched memory:', data);
        updateCharacterPosition(data.current_room);
    })
    .catch(error => {
        console.error('Error fetching memory:', error);
    });
}

/**
 * Updates the character's position on the webpage based on the current room.
 * @param {string} current_room - The current room name.
 */
function updateCharacterPosition(current_room) {
    console.log('Updating character position to:', current_room);
    current_room = current_room.toLowerCase().replace(/\s+/g, '_');
    
    const availableRooms = ['kitchen', 'living_room', 'bedroom', 'study', 'library', 'garden'];
    if (!availableRooms.includes(current_room)) {
        console.error(`Invalid room: ${current_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 === current_room) {
        console.log('Character is already in', current_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(current_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(`Updated character position to ${current_room}`);
    }
}

/**
 * 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');
    }
}

// Call this when the page loads
document.addEventListener('DOMContentLoaded', initializeCharacter);