We have this code base, please tell us exactly what and how we need to change in it to implement these new functionalities. You can make new py files if necessary. 

Make a highly modular implementation.
Introduce Events: We wanted to introduce a new system where the AI agent can interact with events in its environment. These events could be triggered by the user or occur randomly.
Event Handling: When an event occurs, it should update the AI's context (memory) and clear the event from memory once it's done.
Current Event Tracking: There should be a section in the memory to track the current event.
Example Event: We wanted to create a simple example event where a bell rings in the living room, and the AI should check it.
Conditional Actions: The AI should only see and use the new possible actions when the event is occurring and the AI is in the correct specified room for each different event. 
Random Outcomes: When the AI opens the door after the bell rings, there should be random outcomes (e.g., finding mail, a cat running away).
User Interaction: Users should be able to trigger events manually.
Immediate Response: When any event is called (either by the user or randomly), the AI should immediately respond with a message generated by its autonomous process.
Context Update: The AI should receive the new context information (e.g., the bell ringing) and use this information to generate an immediate response.



# app.py

import os
import json
from flask import Flask, request, jsonify, render_template, Response
from openai import OpenAI
from functions import sleep_action, write_poem, take_note, move, ponder_about, associations, process_result, de_focus
from memory import MemoryManager
from datetime import datetime
import logging
import threading
import time
from typing import Dict, Any
import configparser
import queue
import atexit
import asyncio  # Needed if you decide to make some functions async
import configparser




# Initialize environment variables
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)
memory_manager = MemoryManager()

# Start periodic saving every so often
memory_manager.start_periodic_saving(interval_seconds=140)

def load_config():
    config = configparser.ConfigParser()
    config.read('config.ini')  # Load the INI file

    return config

# Load the configuration
config = load_config()

# Get the value and convert it to an int
autonomous_rate = config.getint('rate', 'autonomous_rate')

print(autonomous_rate)

# Register shutdown hook to save memory
def save_memory_on_shutdown():
    memory_manager.save_memory()
    logger.info("Memory state saved on shutdown.")

atexit.register(save_memory_on_shutdown)

# Initialize the queue for autonomous actions
autonomous_actions_queue = queue.Queue()

# -------------------------------
# Logging Configuration
# -------------------------------
logging.basicConfig(
    level=logging.INFO,  # Set to DEBUG for detailed logs
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    handlers=[
        logging.StreamHandler(),  # Logs to console
        logging.FileHandler("logs/db_log_archive.log")  # Logs to file
    ]
)

logger = logging.getLogger(__name__)

# -------------------------------
# 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": "Process language, information or data from your memory, recent ideas, thoughts, poems or any other output can be processed here to 'think' about it, improve it archive it or present as a final result.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project_data": {
                        "type": "string",
                        "description": "Data, thoughts, dieas and instructions for the exact process for this data, with all the information and instructions to process in the desired way."
                    }
                },
                "required": ["project_data"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "associations",
            "description": "Let your mind wander, remember and identify patterns, memories and emotions, 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": "Get ideas for 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 a note to archival information that you find important. Any kind of data you might want to save is accepted here.",
            "parameters": {
                "type": "object",
                "properties": {
                    "content": {
                        "type": "string",
                        "description": "The content of the archival."
                    }
                },
                "required": ["content"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "ponder_about",
            "description": "Let your mind focus on a topic, get creative ideas, bizarre and abstract, get your internal chain of thought, based on the input",
            "parameters": {
                "type": "object",
                "properties": {
                    "idea": {
                        "type": "string",
                        "description": "The set of ideas, thoughts, concepts and memories for the chain of thought"
                    }
                },
                "required": ["idea"],
                "additionalProperties": False
            }
        }
    },
        {
        "type": "function",
        "function": {
            "name": "de_focus",
            "description": "Defocused attention, get random, unrelated ideas, absurd concepts from your subconscious, helpful for changing perspectives, behaviors, actions thoughts or getting new insight, objectives or to relax.  ",
            "parameters": {
                "type": "object",
                "properties": {
                    "meditate": {
                        "type": "string",
                        "description": "free your mind of thoughts and input three dots in here"
                    }
                },
                "required": ["meditate"],
                "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
# -------------------------------
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,
    "de_focus": de_focus
}

# Function to load the configuration from an INI file
def load_config():
    config = configparser.ConfigParser()
    config.read('config.ini')  # Load the INI file
    return config

# Load the configuration
config = load_config()
logger.info(f"Loaded models from config: {dict(config['models'])}")

# Accessing the models from the config file
model1 = config['models']['model4']
model2 = config['models']['model5']
model3 = config['models']['model6']
model4 = config['models']['model7']

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

def execute_function(function_name: str, arguments: Dict[str, Any], is_autonomous: bool = False) -> Dict[str, Any]:
    """
    Executes the specified function and integrates its output into memory.
    Returns the function's content 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 only if it's autonomous
    if is_autonomous:
        autonomous_actions_queue.put({
            "type": func_output["action_type"],  # Flattening the structure
            "content": func_output["content"]
        })

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

    # Return the content part of func_output to match frontend expectations
    return 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)
    logger.info(f"User exchange added: {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"You can only take one action per turn"
                f"You are set to run in a timer without limit, allowing you to take actions in steps across time."
                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.debug(f"Prepared messages for OpenAI: {messages}")

    try:
        response = client.chat.completions.create(
            model=model1,  # 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:
            # Execute function without enqueuing to SSE
            func_output = execute_function(function_name, arguments, is_autonomous=False)
            actions.append({
                "type": function_name,
                "content": func_output  # Already the 'content' part
            })
        except ValueError as ve:
            logger.warning(str(ve))
            # Only add to exchanges if it's a direct response to user
            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)
        }

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

            # **Handle specific action confirmations if needed**
            # Example for 'move' action
            if function_name == "move" and func_output.get("confirmation"):
                confirmation_message = func_output.get("confirmation", "")
                room_description = func_output.get("room_description", "")
                final_reply = f"{confirmation_message}\n{room_description}\n{final_reply}"

            logger.debug(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 as part of user-AI exchange
        memory_manager.add_exchange("assistant", final_reply)
        logger.info(f"Assistant exchange added: {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(autonomous_rate)  # Wait for specified 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 with a thought processing system, integrated with different models simulating a brain's functions. "
                    f"You can take actions in steps across different responses, consider the chronological order or your actions."
                    f"You can only take one action per turn"
                    f"You have access to tools for creative pondering, associative ideation, and other cognitive functions. "
                    f"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"
                    f"Please choose your next action from the available actions, ensuring that you consider your recent past interactions, thoughts, notes, current state and objective in memories so you have a realistic behaviour with consistency and achiving progress."
                )
            },
            {"role": "user", "content": "Choose your next action from the available actions and provide the necessary parameters."}
        ]

        logger.debug("autonomous_decision: Prepared prompt for OpenAI.")

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

        choice = response.choices[0]
        message = choice.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.debug(f"Autonomous action requested: {function_name} with arguments {arguments}")

            try:
                # Execute function with enqueuing to SSE
                func_output = execute_function(function_name, arguments, is_autonomous=True)
            except ValueError as ve:
                logger.warning(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)
            }

            # Get the final assistant reply
            try:
                final_response = client.chat.completions.create(
                    model=model4,
                    messages=[
                        {
                            "role": "system",
                            "content": (
                                f"You are an autonomous AI agent designed to have a thought processing system, integrated with different models simulating functions of a brain. "
                                f"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"You can take actions in steps across different responses, consider the chronological order or your actions."
                                f"You can only take one action per turn"
                                f"Current time is {current_time}. Memory: {memory}\nWorld state: {world_state}\n"
                                f"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."
                                f"Please do not repeat excessively the same action in a row. Always aim for the objective defined to acomplish "
                                f"The following input is your last internal process."
                            )
                        },
                        {"role": "user", "content": f"Internal process input: {function_response}"}
                    ],
                    temperature=1.1,
                )
                final_reply = final_response.choices[0].message.content

                # **Handle specific action confirmations if needed**
                if function_name == "move" and func_output.get("confirmation"):
                    confirmation_message = func_output.get("confirmation", "")
                    room_description = func_output.get("room_description", "")
                    final_reply = f"{confirmation_message}\n{room_description}\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

            # Enqueue the final_reply to be sent via SSE
            autonomous_actions_queue.put({
                "type": "final_reply",
                "content": final_reply
            })
            logger.debug("Autonomous assistant reply enqueued for SSE.")

            # Integrate the assistant's reply as an autonomous action
            memory_manager.integrate_action({
                "action_type": "autonomous_reply",
                "content": final_reply,
                "notes": ""
            })
            logger.debug("Autonomous assistant reply integrated into memory.")
        else:
            # Handle cases where the model didn't choose a function call
            assistant_reply = message.content if message.content else ""
            logger.debug(f"Autonomous assistant reply without function call: {assistant_reply}")
            # Integrate the assistant's reply as an autonomous action
            memory_manager.integrate_action({
                "action_type": "autonomous_reply",
                "content": assistant_reply,
                "notes": ""
            })
            logger.debug("Autonomous assistant reply without function call integrated into memory.")

# -------------------------------
# Start Autonomous Decision Thread
# -------------------------------
autonomous_thread = threading.Thread(target=autonomous_decision, daemon=True)
autonomous_thread.start()
logger.info("Autonomous decision-making thread started.")

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


# 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
import configparser


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)

# Function to load the configuration from an INI file
def load_config():
    config = configparser.ConfigParser()
    config.read('config.ini')  # Load the INI file

    return config

# Load the configuration
config = load_config()

# Accessing the models from the config file
model1 = config['models']['model1']
model2 = config['models']['model2']
model3 = config['models']['model3']
model4 = config['models']['model9']


# 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]:
    """
    Process data in different ways for the AI.
    """
    try:
        completion = client.chat.completions.create(
            model=model4,
           messages=[
                {"role": "system", "content": """
                 You are given different amounts of data and instructions to process it in different ways, make sure to present the information
                 back and go on with the process specified in the prompt with it. Consider any information and make your output from a first person perspective in a self-referential way.
              
"""
                 
                 
                 },
                {"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 = 1) -> 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=model1,
                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 = 1) -> 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=model2,
                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 de_focus(meditate: str, num_responses: int = 1) -> 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=model2,
                messages=[
                    {"role": "system", "content": "You are a bot that randomly generates creative, absurd, philosophical, bizarre, profound, and unique phrases on demand."},
                    {"role": "user", "content": meditate}
                ],
                temperature=1.1
            )
            response = completion.choices[0].message.content
            thoughts_list.append(response)
        
        defocussed = "\n\n".join(thoughts_list)  # Combine responses with a separator
        logger.info(f"Generated pondering for '{meditate}': '{defocussed}'")
    except Exception as e:
        defocussed = f"An error occurred while generating the pondering: {str(e)}"
        logger.error(defocussed)
    
    return FunctionOutput(
        action_type="de_focus",
        content={"defocussed": defocussed},
        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 = 1) -> 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=model3,
                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 poem ideas 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()

# functions.py

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,
            "room_description": room_text,
            "confirmation": f"Moved to the {to_room.replace('_', ' ')}."
        },
        notes=""  # You can keep this empty or remove it if not used elsewhere
    ).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
import configparser
import threading
import time
import shutil

# Load environment variables from .env file
load_dotenv()

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)  # Set to DEBUG for detailed logs

# Function to load the configuration from an INI file
def load_config():
    config = configparser.ConfigParser()
    config.read('config.ini')  # Load the INI file
    return config

# Load the configuration
config = load_config()

# Accessing the models from the config file
model = config['models']['model8']
recent_acts = config.getint('memory', 'recent_acts')
short_term = config.getint('memory', 'short_term')

class MemoryManager:
    def __init__(self, 
                 memory_db_path="memory/memory_db.json",
                 notes_db_path="notes/notes_db.json", 
                 results_db_path="results/results_db.json"):
        # Initialize memory sections without maxlen for manual control
        self.recent_actions = deque()
        self.short_term_memory = deque()
        self.long_term_memory = []
        self.exchanges = deque(maxlen=60)  # Assuming exchanges are limited to 60

        # Define maximum capacities
        self.max_recent_actions = recent_acts
        self.max_short_term_memory = short_term

        self.total_time_spent = timedelta()
        self.current_room = "bedroom"  # Initialize starting room
        self.notes_db_path = notes_db_path
        self.results_db_path = results_db_path
        self.memory_db_path = memory_db_path  # Path for memory persistence

        # Create directories if they don't exist
        os.makedirs(os.path.dirname(self.memory_db_path), exist_ok=True)
        os.makedirs(os.path.dirname(self.notes_db_path), exist_ok=True)
        os.makedirs(os.path.dirname(self.results_db_path), exist_ok=True)

        # Initialize a re-entrant lock for thread-safe operations
        self.lock = threading.RLock()

        # Load existing memory, notes, and results if available
        self.load_memory()
        self.load_notes()
        self.load_results()

        ## Databases Initialization

        # Initialize notes database if it doesn't exist
        if not os.path.isfile(self.notes_db_path):
            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}")

        # Initialize results database if it doesn't exist
        if not os.path.isfile(self.results_db_path):
            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}")

    def add_exchange(self, role: str, content: str):
        """
        Adds a user or assistant exchange to the exchanges deque.
        """
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        exchange_entry = {
            "timestamp": timestamp,
            "role": role,
            "content": content
        }
        with self.lock:
            self.exchanges.append(exchange_entry)

        logger.debug(f"Exchange added: {exchange_entry}")

    def generate_summary(self, data: Any, summary_type: str = "general") -> str:
        """
        Generates a summary using the OpenAI API.
        """
        if summary_type == "exchanges_summary":
            prompt_description = "Summarize the following user and assistant exchanges."
            formatted_data = "\n".join([f"{ex['role'].capitalize()}: {ex['content']}" for ex in data])
        elif summary_type == "actions_summary":
            prompt_description = "Summarize the following recent actions taken by the assistant."
            formatted_data = "\n".join([
                f"{action['timestamp']} - {action['action_type']}: {action['content']}" for action in data
            ])
        else:
            prompt_description = "Summarize the following data."
            formatted_data = "\n".join([str(d) for d in data])

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

        try:
            openai_api_key = os.getenv("OPENAI_API_KEY")
            if not openai_api_key:
                logger.error("OpenAI API key not set.")
                raise ValueError("OpenAI API key not set.")

            client = OpenAI(api_key=openai_api_key, timeout=10)

            completion = client.chat.completions.create(
                model=model,  # Ensure this model name is correct
                messages=[
                    {
                        "role": "system",
                        "content": "You are a NLP unit of an autonomous AI agent, summarize and archive the important data, events, ideas, thoughts, actions, objectives and current progress, etc., use  a self-referencing first person style."
                    },
                    {"role": "user", "content": prompt}
                ],
                max_tokens=700,
                temperature=0.5,
            )

            summary = completion.choices[0].message.content.strip()
            logger.info(f"Generated memory aggregation: {summary}")
            return summary
        except Exception as e:
            logger.error(f"generate_summary: Unexpected error: {e}")
            return "Summary could not be generated due to an unexpected error."

    def compress_short_term_memory(self):
        """
        Compresses the short-term memory into a summary and appends it to long-term memory.
        """
        logger.debug("compress_short_term_memory: Start")
        start_time = time.time()
        with self.lock:
            short_term_list = list(self.short_term_memory)
            if not short_term_list:
                logger.info("compress_short_term_memory: Short-Term Memory is empty. No compression needed.")
                logger.debug("compress_short_term_memory: End")
                return

            # Generate the summary synchronously
            summary = self.generate_summary(short_term_list, summary_type="actions_summary")

            if summary.startswith("Summary could not be generated"):
                logger.warning("compress_short_term_memory: Summary generation failed. Skipping addition to Long-Term Memory.")
                logger.debug("compress_short_term_memory: End")
                return

            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("compress_short_term_memory: Short-term memory compressed into a summary and added to long-term memory.")

            # Clear the short-term memory deque after compression
            self.short_term_memory.clear()
            logger.debug("compress_short_term_memory: Short-Term Memory cleared after compression.")

        end_time = time.time()
        logger.debug(f"compress_short_term_memory: End (Duration: {end_time - start_time:.2f} seconds)")

    def integrate_action(self, action_output: Dict[str, Any]):
        """
        Integrates the standardized function output into the memory system.
        Ensures that each action is recorded only once and manages memory limits.
        """
        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")

        # Create a comprehensive action entry
        action_entry = {
            "timestamp": timestamp,
            "action_type": action_type,
            "content": content,
            "notes": notes
        }

        with self.lock:
            # Append to recent_actions
            self.recent_actions.append(action_entry)
            logger.debug(f"Action appended to recent_actions: {action_entry}")

            # Check if recent_actions exceeds its max capacity
            if len(self.recent_actions) > self.max_recent_actions:
                oldest_recent = self.recent_actions.popleft()
                self.short_term_memory.append(oldest_recent)
                logger.debug(f"Moved oldest from recent_actions to short_term_memory: {oldest_recent}")

                # Check if short_term_memory exceeds its max capacity
                if len(self.short_term_memory) > self.max_short_term_memory:
                    self.compress_short_term_memory()

        # Handle specific action types
        if action_type == "move":
            to_room = content.get("to_room", self.current_room)
            self.current_room = to_room  # Update current room
            logger.info(f"Current room updated to: {self.current_room}")
        elif action_type == "take_note":
            note = content.get("note", "")
            self.save_note(note)
        elif action_type == "process_result":
            project_results = content.get("projectResult", "")
            self.save_final_result(project_results)
        # Add more action-specific handlers as needed

        self.save_memory()  # Save after integrating action

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

    def save_final_result(self, f_result: str):
        """
        Saves final results into the JSON database.
        """
        try:
            with self.lock:
                #self.backup_file(self.results_db_path)
                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)
                    f.truncate()
            logger.info(f"Final results saved: {f_result}")
        except Exception as e:
            logger.error(f"Failed to save final results: {e}")

    def backup_file(self, file_path: str):
        """
        Creates a backup of the specified file with a timestamp.
        """
        if os.path.isfile(file_path):
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            backup_path = f"{file_path}.{timestamp}.bak"
            try:
                shutil.copy(file_path, backup_path)
                logger.info(f"Backup created for {file_path} at {backup_path}")
            except Exception as e:
                logger.error(f"Failed to create backup for {file_path}: {e}")

    def get_memory(self) -> str:
        """
        Returns a string representation of the memory including long-term memories,
        short-term memories, recent actions, exchanges, and current location.
        """
        memory = ""
        with self.lock:
            if self.long_term_memory:
                memory += "Long-Term Memory:\n"
                for entry in self.long_term_memory:
                    if "type" in entry:
                        memory += f"[{entry['timestamp']}] ({entry['type']}) {entry['summary']}\n"
                    else:
                        memory += f"[{entry['timestamp']}] (unknown_type) {entry.get('summary', 'No summary available.')}\n"
                memory += "\n"
            memory += "Short-Term Memory:\n"
            for action in self.short_term_memory:
                memory += f"[{action['timestamp']}] {action['action_type']}: {action['content']}\n"
            memory += "\n"
            memory += "Recent Actions:\n"
            for action in self.recent_actions:
                memory += f"[{action['timestamp']}] {action['action_type']}: {action['content']}\n"
            memory += "\n"
            if self.exchanges:
                memory += "User-AI Exchanges:\n"
                for ex in self.exchanges:
                    memory += f"[{ex['timestamp']}] {ex['role'].capitalize()}: {ex['content']}\n"
            memory += f"\nCurrent Location: {self.current_room.capitalize()}"
        return memory

    def save_memory(self):
        """
        Saves the entire memory state to a JSON file.
        """
        with self.lock:
            memory_state = {
                "recent_actions": list(self.recent_actions),
                "short_term_memory": list(self.short_term_memory),
                "long_term_memory": self.long_term_memory,
                "exchanges": list(self.exchanges),
                "total_time_spent_seconds": self.total_time_spent.total_seconds(),
                "current_room": self.current_room
            }
            try:
                with open(self.memory_db_path, 'w') as f:
                    json.dump(memory_state, f, indent=4)
                logger.debug("Memory state saved successfully.")
            except Exception as e:
                logger.error(f"Failed to save memory state: {e}")

    def load_memory(self):
        """
        Loads the memory state from a JSON file if it exists.
        """
        if os.path.isfile(self.memory_db_path):
            try:
                with self.lock:
                    with open(self.memory_db_path, 'r') as f:
                        memory_state = json.load(f)
                self.recent_actions = deque(memory_state.get("recent_actions", []))
                self.short_term_memory = deque(memory_state.get("short_term_memory", []))
                self.long_term_memory = memory_state.get("long_term_memory", [])
                self.exchanges = deque(memory_state.get("exchanges", []), maxlen=60)
                self.total_time_spent = timedelta(seconds=memory_state.get("total_time_spent_seconds", 0))
                self.current_room = memory_state.get("current_room", "bedroom")
                logger.info("Memory state loaded successfully.")
            except Exception as e:
                logger.error(f"Failed to load memory state: {e}")
        else:
            logger.info("No existing memory state found. Starting fresh.")

    def load_notes(self):
        """
        Loads notes from the JSON database into long-term memory.
        """
        if os.path.isfile(self.notes_db_path):
            try:
                with self.lock:
                    with open(self.notes_db_path, 'r') as f:
                        data = json.load(f)
                notes = data.get("notes", [])
                for note in notes:
                    note_content = f"[{note['timestamp']}] Note: {note['content']}"
                    self.long_term_memory.append({
                        "type": "note",
                        "timestamp": note['timestamp'],
                        "summary": note_content
                    })
                logger.info("Notes loaded successfully.")
            except Exception as e:
                logger.error(f"Failed to load notes: {e}")
        else:
            logger.info("No existing notes found.")


    def load_results(self):
        """
        Loads results from the JSON database into long-term memory.
        """
        if os.path.isfile(self.results_db_path):
            try:
                with self.lock:
                    with open(self.results_db_path, 'r') as f:
                        data = json.load(f)
                results = data.get("results", [])
                for result in results:
                    result_content = f"[{result['timestamp']}] Result: {result['content']}"
                    self.long_term_memory.append({
                        "type": "result",
                        "timestamp": result['timestamp'],
                        "summary": result_content
                    })
                logger.info("Results loaded successfully.")
            except Exception as e:
                logger.error(f"Failed to load results: {e}")
        else:
            logger.info("No existing results found.")

    def start_periodic_saving(self, interval_seconds: int = 300):
        """
        Starts a background thread that saves memory at regular intervals.
        """
        def save_periodically():
            while True:
                time.sleep(interval_seconds)
                self.save_memory()

        thread = threading.Thread(target=save_periodically, daemon=True)
        thread.start()
        logger.info(f"Started periodic memory saving every {interval_seconds} seconds.")




<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>LLM Controlled Environment</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <main>
            <!-- Central Display Area -->
            <div id="central-display">
                <div id="room-background">
                    <div class="character" id="character"></div>
                    <!-- Message Bubbles Container -->
                    <div id="message-bubbles"></div>
                </div>
                <div class="room-label" id="current-room-label">Bedroom</div>
            </div>

            <!-- Chatbox Area -->
            <div id="chat-container">
                <div id="chat"></div>
                <div id="input-area">
                    <input type="text" id="input" placeholder="Enter your message here..." autofocus>
                    <button id="send">Send</button>
                </div>
            </div>
        </main>

        <footer>
            <!-- Possible Rooms Section -->
            <div id="possible-rooms-container">
                <ul id="possible-rooms">
                    <li data-room="kitchen">Kitchen</li>
                    <li data-room="living_room">Living Room</li>
                    <li data-room="bedroom">Bedroom</li>
                    <li data-room="study">Study</li>
                    <li data-room="library">Library</li>
                    <li data-room="garden">Garden</li>
                    <!-- Add more rooms as needed -->
                </ul>
            </div>

            <!-- Theme Selection Section -->
            <div id="theme-selector">
                <select id="theme-dropdown">
                    <option value="default">Bubblegum Dream</option>
                    <option value="dark">Midnight Study</option>
                    <option value="pastel">Pastel Paradise</option>
                    <option value="aero">Aero Palace</option>
                    <option value="forest">Forest Whisper</option>
                    <option value="cosmic">Cosmic Glow</option>
                    <option value="autumn">Autumn Bliss</option>
                    <option value="cyberpunk">Cyberpunk</option>



                    <!-- Add more themes as needed -->
                </select>
            </div>
        </footer>
    </div>

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



/// static/script.js

// Function to escape HTML special characters
function escapeHtml(text) {
    const map = {
        '&': "&amp;",
        '<': "&lt;",
        '>': "&gt;",
        '"': "&quot;",
        "'": "&#039;",
        '\n': "<br>"
    };
    return text.replace(/[&<>"'\n]/g, function(m) { return map[m]; });
}

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

// Event listener for selecting possible rooms
document.getElementById('possible-rooms').addEventListener('click', function(e) {
    if (e.target && e.target.nodeName === "LI") {
        const room = e.target.getAttribute('data-room');
        sendMessageToMove(room);
    }
});

// Event listener for theme selection
document.getElementById('theme-dropdown').addEventListener('change', function(e) {
    const selectedTheme = e.target.value;
    applyTheme(selectedTheme);
});

/**
 * 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);
            showBubble(data.reply);
        }
    })
    .catch(error => {
        console.error('Error:', error);
        appendMessage("Error", "Failed to send the message.", 'error');
    });
}

/**
 * Sends a move command to the backend when a room is selected.
 * @param {string} room - The target room's ID.
 */
function sendMessageToMove(room) {
    const moveCommand = `Move to ${capitalizeRoom(room)}`;
    appendMessage("User", moveCommand, 'user');

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

/**
 * Appends a message to the chat container with intelligent scrolling.
 * @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 isAtBottom = chat.scrollHeight - chat.clientHeight <= chat.scrollTop + 1;

    const msg = document.createElement('div');
    msg.classList.add(type);

    // Escape HTML
    const escapedText = escapeHtml(text);

    if (type === 'user') {
        msg.innerHTML = `<strong>${sender}:</strong> ${escapedText}`;
    } else if (type === 'assistant') {
        msg.innerHTML = `<strong>${sender}:</strong> ${escapedText}`;
    } else if (type === 'assistant-action') {
        msg.innerHTML = `<div class="assistant-action"><strong>${sender}:</strong> ${escapedText}</div>`;
    } else if (type === 'error') {
        msg.innerHTML = `<strong>${sender}:</strong> ${escapedText}`;
    }

    chat.appendChild(msg);

    // Scroll only if the user is at the bottom
    if (isAtBottom) {
        chat.scrollTop = chat.scrollHeight;
    }
}

/**
 * Displays a temporary message bubble above the character.
 * @param {string} message - The message text to display.
 */
function showBubble(message) {
    const bubblesContainer = document.getElementById('message-bubbles');
    const bubble = document.createElement('div');
    bubble.classList.add('message-bubble');
    bubble.innerHTML = escapeHtml(message);

    bubblesContainer.appendChild(bubble);

    // Remove the bubble after 5 seconds
    setTimeout(() => {
        bubble.remove();
    }, 5000);
}

/**
 * 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');
                showBubble(`Moved to ${capitalizeRoom(action.content.to_room)}.`);
                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');
                showBubble(`Slept for ${action.content.duration} seconds.`);
                break;
            case 'process_result':
                appendMessage("Assistant", `Processed result: ${action.content.projectResult}`, 'assistant-action');
                showBubble("Processed the result.");
                break;
            case 'associations':
                appendMessage("Assistant", `Generated associations: ${action.content.associations}`, 'assistant-action');
                showBubble("Generated some associations.");
                break;
            case 'ponder_about':
                appendMessage("Assistant", `Pondered about: ${action.content.CoT}`, 'assistant-action');
                showBubble("I'm pondering...");
                break;
            case 'de_focus':
                appendMessage("Assistant", `Meditated: ${action.content.defocussed}`, 'assistant-action');
                showBubble("I'm meditating...");
                break;
            case 'error':
                appendMessage("Assistant", `Error: ${action.content.message}`, 'error');
                showBubble("An error occurred.");
                break;
            // Add more cases for other action types as needed
            default:
                console.warn(`Unknown action type: ${action.type}`);
        }
    });
}

/**
 * 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);
        if (action.type === 'final_reply' && action.content) {
            showBubble(action.content);
        }
    };

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

/**
 * 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');
            showBubble(`Moved to ${capitalizeRoom(action.content.to_room)}.`);
            break;
        case 'write_poem':
            displayPoem(action.content.poem);
            showBubble("Here's a poem for you!");
            break;
        case 'take_note':
            displayNote(action.content.note);
            showBubble("Note taken.");
            break;
        case 'sleep':
            appendMessage("Assistant", `Slept for ${action.content.duration} seconds.`, 'assistant-action');
            showBubble(`Slept for ${action.content.duration} seconds.`);
            break;
        case 'process_result':
            appendMessage("Assistant", `Processed result: ${action.content.projectResult}`, 'assistant-action');
            showBubble("Processed the result.");
            break;
        case 'associations':
            appendMessage("Assistant", `Generated associations: ${action.content.associations}`, 'assistant-action');
            showBubble("Generated some associations.");
            break;
        case 'ponder_about':
            appendMessage("Assistant", `Pondered about: ${action.content.CoT}`, 'assistant-action');
            showBubble("I'm pondering...");
            break;
        case 'de_focus':
            appendMessage("Assistant", `Meditated: ${action.content.defocussed}`, 'assistant-action');
            showBubble("I'm meditating...");
            break;
        case 'error':
            appendMessage("Assistant", `Error: ${action.content.message}`, 'error');
            showBubble("An error occurred.");
            break;
        case 'final_reply':
            appendMessage("Assistant", action.content, 'assistant');
            showBubble(action.content);
            break;
        default:
            console.warn(`Unknown autonomous action type: ${action.type}`);
    }
}

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

/**
 * Moves the character to the specified room by changing the background image.
 * @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 roomBackground = document.getElementById('room-background');
    const roomLabel = document.getElementById('current-room-label');

    // Update background image based on the room
    roomBackground.style.backgroundImage = `url('/static/images/${room}.jpg')`; // Ensure you have images for each room
    roomLabel.textContent = capitalizeRoom(room);

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

/**
 * Displays a poem in the chat container.
 * @param {string} poemText - The poem text.
 */
function displayPoem(poemText) {
    appendMessage("Assistant", `*${poemText}*`, 'assistant-action');
    showBubble("Here's a poem for you!");
}

/**
 * Displays a note in the chat container.
 * @param {string} noteText - The note text.
 */
function displayNote(noteText) {
    appendMessage("Assistant", `Note: ${noteText}`, 'assistant-action');
    showBubble("Note taken.");
}

/**
 * Fetches the current room from the backend and positions the character.
 */
function initializeCharacter() {
    fetch('/api/memory')
        .then(response => response.json())
        .then(data => {
            const currentRoom = data.current_room;
            moveCharacter(currentRoom);
            appendMessage("System", `Character initialized in ${capitalizeRoom(currentRoom)}.`, 'assistant-action');
            showBubble(`Hello! I'm in the ${capitalizeRoom(currentRoom)}.`);
        })
        .catch(error => {
            console.error('Error fetching current room:', error);
            // Default to bedroom if there's an error
            moveCharacter('bedroom');
            appendMessage("Error", "Failed to fetch current room. Defaulting to Bedroom.", 'error');
            showBubble("Oops! Defaulting to Bedroom.");
        });
}

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

/**
 * Applies the selected theme by changing CSS variables.
 * @param {string} theme - The selected theme identifier.
 */
function applyTheme(theme) {
    switch(theme) {
        case 'default':
            setThemeDefaults();
            break;
        case 'dark':
            setThemeDark();
            break;
        case 'pastel':
            setThemePastel();
            break;
        case 'aero':
            setThemeAero();
            break
        case 'forest':
            setForestWhisperTheme();
            break
        case 'cosmic':
            setCosmicGlowTheme();
            break
        case 'autumn':
            setAutumnBlissTheme();
            break
        case 'cyberpunk':
            setCyberpunkPopTheme();
            break
            


/** setCyberpunkPopTheme   cyberpunk */
        
        // Add more cases for additional themes
        default:
            setThemeDefaults();
    }
}

/**
 * Sets the default Bubblegum Dream theme.
 */
function setThemeDefaults() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(173, 216, 230, 0.7)');
    document.documentElement.style.setProperty('--secondary-color', 'rgba(135,206,250,0.5)');
    document.documentElement.style.setProperty('--accent-color', '#173b13');
    document.documentElement.style.setProperty('--outline-color', '#f596ff');
    document.documentElement.style.setProperty('--background-color', '#E0F7FA');
    document.documentElement.style.setProperty('--character-color', '#f596ff');
    document.documentElement.style.setProperty('--button-color', '#f2dc18');
    document.documentElement.style.setProperty('--button-hover-color', '#63B3ED');
    document.documentElement.style.setProperty('--main-frame-color', '#ecf010');
    document.documentElement.style.setProperty('--filter-color', '#ecf010');

}

/**
 * Sets the Midnight Blue theme.
 */
function setThemeDark() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(255,186,0,0.21)'); /* RGBA */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(10,53,84,0.2)'); /* RGBA */
    document.documentElement.style.setProperty('--accent-color', '#ffb800'); /* HEX */
    document.documentElement.style.setProperty('--outline-color', '#606060'); /* HEX */
    document.documentElement.style.setProperty('--background-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--character-color', '#ffb700'); /* HEX */
    document.documentElement.style.setProperty('--button-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--button-hover-color', '#ffd400'); /* HEX */
    document.documentElement.style.setProperty('--main-frame-color', '#594D38');
    document.documentElement.style.setProperty('--filter-color', '#94775d');


}

/**
 * Sets the Pastel Paradise theme.
 */
function setThemePastel() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(255, 182, 193, 0.7)'); /* Light Pink */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(255, 192, 203, 0.5)'); /* Pink */
    document.documentElement.style.setProperty('--accent-color', '#333333');
    document.documentElement.style.setProperty('--outline-color', '#FF69B4'); /* Hot Pink */
    document.documentElement.style.setProperty('--background-color', '#FFE4E1'); /* Misty Rose */
    document.documentElement.style.setProperty('--character-color', '#FFB6C1'); /* Light Pink */
    document.documentElement.style.setProperty('--button-color', '#FF69B4'); /* Hot Pink */
    document.documentElement.style.setProperty('--button-hover-color', '#FF1493'); /* Deep Pink */
    document.documentElement.style.setProperty('--main-frame-color', '#F4DFFF');
    document.documentElement.style.setProperty('--filter-color', '#FF69B4');


}

/**
 * Sets the Aero theme.
 */
function setThemeAero() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(0,255,153,0.2)'); /* Aero Green */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(102,255,255,0.2)'); /* Aero Blue */
    document.documentElement.style.setProperty('--accent-color', '#06a7ef');
    document.documentElement.style.setProperty('--outline-color', '#02f580'); /*  */
    document.documentElement.style.setProperty('--background-color', '#96FFFC'); /* */
    document.documentElement.style.setProperty('--character-color', '#02f580'); /**/
    document.documentElement.style.setProperty('--button-color', '#ffe203'); /*  */
    document.documentElement.style.setProperty('--button-hover-color', '#f4ffc7'); /*  */
    document.documentElement.style.setProperty('--main-frame-color', '#FFFFFF');
    document.documentElement.style.setProperty('--filter-color', '#ecf010');


}

function setForestWhisperTheme() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(0,187,0,0.3)'); /* RGBA */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(153,231,36,0.3)'); /* RGBA */
    document.documentElement.style.setProperty('--accent-color', '#693300'); /* HEX */
    document.documentElement.style.setProperty('--outline-color', '#6f2f15'); /* HEX */
    document.documentElement.style.setProperty('--background-color', '#7de889'); /* HEX */
    document.documentElement.style.setProperty('--character-color', '#6f2f15'); /* HEX */
    document.documentElement.style.setProperty('--button-color', '#6b2a15'); /* HEX */
    document.documentElement.style.setProperty('--button-hover-color', '#ffffff'); /* HEX */
    document.documentElement.style.setProperty('--main-frame-color', '#1B9200');
    document.documentElement.style.setProperty('--filter-color', '#1B9200');


}

function setCosmicGlowTheme() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(72, 61, 139, 0.8)'); // Dark Slate Blue
    document.documentElement.style.setProperty('--secondary-color', 'rgba(123, 104, 238, 0.5)'); // Medium Slate Blue
    document.documentElement.style.setProperty('--accent-color', '#FF00FF'); // Magenta
    document.documentElement.style.setProperty('--outline-color', '#00FFFF'); // Cyan
    document.documentElement.style.setProperty('--background-color', '#000000'); // Black
    document.documentElement.style.setProperty('--character-color', '#7FFFD4'); // Aquamarine
    document.documentElement.style.setProperty('--button-color', '#FFD700'); // Gold
    document.documentElement.style.setProperty('--button-hover-color', '#FF4500'); // Orange Red
    document.documentElement.style.setProperty('--main-frame-color', '#000000');
    document.documentElement.style.setProperty('--filter-color', '#62486e');


}

function setAutumnBlissTheme() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(135,80,0,0.37)'); /* RGBA */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(78,25,0,0.43)'); /* RGBA */
    document.documentElement.style.setProperty('--accent-color', '#f2b700'); /* HEX */
    document.documentElement.style.setProperty('--outline-color', '#be0000'); /* HEX */
    document.documentElement.style.setProperty('--background-color', '#6a2c00'); /* HEX */
    document.documentElement.style.setProperty('--character-color', '#7db2e1'); /* HEX */
    document.documentElement.style.setProperty('--button-color', '#a00000'); /* HEX */
    document.documentElement.style.setProperty('--button-hover-color', '#ffd400'); /* HEX */
    document.documentElement.style.setProperty('--main-frame-color', '#532700');
    document.documentElement.style.setProperty('--filter-color', '#532700');


}


function setCyberpunkPopTheme() {
    document.documentElement.style.setProperty('--primary-color', 'rgba(0,255,0,.3)'); /* RGBA */
    document.documentElement.style.setProperty('--secondary-color', 'rgba(0,187,255,.3)'); /* RGBA */
    document.documentElement.style.setProperty('--accent-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--outline-color', '#00c480'); /* HEX */
    document.documentElement.style.setProperty('--background-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--character-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--button-color', '#000000'); /* HEX */
    document.documentElement.style.setProperty('--button-hover-color', '#a6fab0'); /* HEX */
    document.documentElement.style.setProperty('--main-frame-color', '#A1C490');
    document.documentElement.style.setProperty('--filter-color', '#a6fab0');


}

/* Additional Themes can be added here following the above pattern */

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