I am building a custom interface for an AI assistant. Write the frontend code using at least React and TailwindCSS. I have the following requirements:
- Interface modelled off a mix of ChatGPT and Claude
- A home page with a "Good morning/afternoon/evening" title and a message box
- When sending a message, the message box moves to the bottom and reveals a full chat interface
- Copy functionality
- Proper markdown formatting
- When code is sent, it can be popped out in a separate side pane to see only the code contents, and to copy them
- Custom system prompt field
- Saveable assistants with custom system prompts
- "Modules" that can be saved as presets and inserted as XML tags into the prompt (eg. <information>, <log>, <instructions>, etc.)
- A sidebar with a full chat history that is persistent even when closing tab
- Light/dark theme

Here is the code for the backend server:
<code>
import logging
import ast
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from gradio_client import Client

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

app = FastAPI()

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Allows all origins
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods
    allow_headers=["*"],  # Allows all headers
)

# Initialize the Gradio Client
client = Client("yuntian-deng/o1mini")


class Message(BaseModel):
    text: str


def extract_response(data):
    parsed_data = ast.literal_eval(data)
    response = parsed_data[0][0][1]
    return response


@app.post("/api/chat")
async def chat(message: Message):
    try:
        logger.info(f"Received message: {message.text}")
        # Send the message to the Hugging Face Space
        response = client.predict(message.text, api_name="/predict")
        logger.info(f"Raw response from Gradio Client: {response}")

        # Adjust the response extraction based on actual response structure
        # For illustration, assuming response is a string
        chatbot_response = response if isinstance(response, str) else str(response)
        logging.info(f"Chatbot response type: {type(chatbot_response)}")

        logger.info(f"Chatbot response: {chatbot_response}")
        return {"response": extract_response(chatbot_response)}
    except Exception as e:
        logger.error("Error processing chat request", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/reset")
async def reset_chat():
    try:
        logger.info("Resetting chat...")
        # Call the reset endpoint of the Gradio client
        reset_response = client.predict(api_name="/reset_textbox")
        logger.info(f"Raw reset response from Gradio Client: {reset_response}")

        # Process the reset response if necessary
        # Adjust the extraction based on the actual reset response structure
        if isinstance(reset_response, str):
            processed_response = reset_response
        else:
            processed_response = str(reset_response)

        logger.info(f"Reset response: {processed_response}")
        return {"status": "Chat has been reset.", "response": processed_response}
    except Exception as e:
        logger.error("Error resetting chat", exc_info=True)
        raise HTTPException(status_code=500, detail="Failed to reset chat.")
</code>