Search code examples
javascriptpythonhtmlcssflask

Unable to type texts to chatbot user input


Description

I am trying to add a chatbot for the backtesting platform built with Flask and there is a button on the right side that toggles the sidebar. If the user clicks the button, it shows the sidebar with a chatbot.

Expected Behavior

The user should be able to type texts into the chatbot user input field.

Current Behavior

Currently unable to type texts into the chatbot user input field.

HTML, CSS, JS

const promptInput = document.getElementById("userInput");
const chatContainer = document.getElementById("chatContainer");
const typingIndicator = document.getElementById("typingIndicator");
const sidebar = document.getElementById("sidebar");
const sidebarContent = document.getElementById("sidebarContent");

async function sendMessage() {
  const prompt = promptInput.value.trim();
  if (!prompt) {
    alert("Please enter a message.");
    return;
  }

  addMessage(prompt, 'user');
  promptInput.value = "";

  showTypingIndicator();

  const generatedText = await generateText(prompt);
  addMessage(generatedText, 'bot');

  hideTypingIndicator();
}

async function generateText(prompt) {
  try {
    const response = await fetch("http://127.0.0.1:5000/generate_text_stream", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ prompt }),
    });

    if (!response.ok) {
      console.error("Error:", response.statusText);
      return "Error occurred while generating response.";
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let isFinished = false;
    let generatedTextContent = "";

    while (!isFinished) {
      const { done, value } = await reader.read();
      if (done) {
        isFinished = true;
        break;
      }
      generatedTextContent += decoder.decode(value, {stream: true});
    }

    return generatedTextContent;
  } catch (error) {
    console.error("Error:", error);
    return "An error occurred.";
  }
}

function addMessage(text, type) {
  const messageDiv = document.createElement("div");
  messageDiv.className = `message ${type}`;
  messageDiv.innerHTML = `<div class="message-bubble fadeIn">${text}</div>`;
  chatContainer.appendChild(messageDiv);

  chatContainer.scrollTop = chatContainer.scrollHeight;

  hideTypingIndicator();
}

let typingTimeout;

function showTypingIndicator() {
  clearTimeout(typingTimeout);
  typingIndicator.style.display = "inline-block";
}

function hideTypingIndicator() {
  typingTimeout = setTimeout(() => {
      typingIndicator.style.display = "none";
  }, 1000);
}

function handleKeyPress(event) {
  if (event.key === "Enter") {
      sendMessage();
  }
}

function toggleSidebar() {
  if (sidebar.style.width === "500px") {
    sidebar.style.width = "0";
    sidebarContent.style.display = "none";
  } else {
    sidebar.style.width = "500px";
    sidebarContent.style.display = "block";
  }
}

window.onload = () => addMessage("Hello! How can I assist you today?", 'bot');
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    text-align: center;
    background-color: #f4f4f4;
}



.container {
    margin-top: 0;
    width: 90%;
    max-width: 450px;
    margin: 10px auto 0;
    background-color: #fff;
    border-radius: 12px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
    padding: 20px;
    transition: all 0.3s;
}

.chat {
    overflow-y: auto;
    height: 400px;
    margin-bottom: 20px;
    border-bottom: 2px solid #e2e2e2;
}

.message {
    display: flex;
    margin-bottom: 12px;
}

.message.user {
    justify-content: flex-end;
}

.message-bubble {
    padding: 12px 18px;
    max-width: 70%;
    border-radius: 20px;
    line-height: 1.6;
    font-size: 0.95rem;
}

.message.user .message-bubble {
    background-color: #3182ce;
    color: white;
}

.message.bot .message-bubble {
    background-color: #e2e2e2;
    color: #333;
}

input[type="text"] {
    width: calc(100% - 110px);
    padding: 12px 18px;
    border: 2px solid #e2e2e2;
    border-radius: 8px 0 0 8px;
    font-size: 1rem;
    outline: none;
    z-index: 2; /* Ensure it's above other elements */
}

.send-button {
    width: 110px;
    background-color: #3182ce;
    color: white;
    padding: 12px 18px;
    border: none;
    border-radius: 0 8px 8px 0;
    cursor: pointer;
    transition: background-color 0.3s;
}

.send-button:hover {
    background-color: #2c5282;
}

.footer {
    text-align: center;
    padding: 15px 0;
    font-size: 0.9rem;
    color: #666;
    position: static;
    border-top: 1px solid #e2e2e2;
    background-color: #fff;
    position: fixed;
    bottom: 0;
    width: 100%;
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.fadeIn {
    animation: fadeIn 1s;
}

@media (max-width: 600px) {
    .container {
        width: 95%;
        margin: 10px auto 0;
    }

    .chat {
        height: 300px;
    }

    input[type="text"],
    .send-button {
        padding: 10px 14px;
        font-size: 0.9rem;
    }

    .footer {
        font-size: 0.8rem;
        margin-top: 30px;
    }
}

.typing-indicator {
    display: none;
    align-items: center;
    justify-content: flex-end;
    margin-top: 8px;
    width: 10px;
    height: 10px;
    background-color: #333;
    border-radius: 50%;
    margin-left: 4px;
    animation: typing 1s infinite;
}

@keyframes typing {
    0%,
    100% {
        transform: scale(1);
        opacity: 1;
    }

    50% {
        transform: scale(1.2);
        opacity: 0.7;
    }
}

/* Sidebar Styles */
.sidebar {
    position: fixed;
    right: 0;
    top: 0;
    height: 100%;
    width: 100%;
    max-width: 500px;
    background-color: #f4f4f4;
    overflow-x: hidden;
    transition: 0.5s;
    padding-top: 60px;
    color: white;
}

.sidebar-content {
    display: none;
}

.sidebar-content h2 {
    text-align: center;
}

.sidebar-content p {
    padding: 10px;
}

.toggle-button {
    position: fixed;
    right: 0;
    top: 0;
    padding: 15px;
    background-color: #3182ce;
    color: white;
    border: none;
    cursor: pointer;
}

.toggle-button:hover {
    background-color: #2c5282;
}





h1 {
    color: rgb(0, 0, 0);
    padding: 20px 0;
    margin: 0 0 20px 0;
}

form {
    display: inline-block;
    background-color: #fff;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

label {
    display: block;
    margin-bottom: 10px;
    font-weight: bold;
}

select {
    width: 100%;
    padding: 10px;
    margin-bottom: 20px;
    border-radius: 5px;
    border: 1px solid #ccc;
}

button {
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    background-color: #4CAF50;
    color: white;
    font-size: 16px;
    cursor: pointer;
}

button:hover {
    background-color: #45a049;
}

img {
    max-width: 90%;
    margin: 20px 0;
    border: 1px solid #ddd;
    border-radius: 10px;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Backtesting Engine</title>
    <link
      href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="{{ url_for('static', filename='css/style.css') }}"
    />
  </head>
  <body>
    <h1 class="text-6xl font-bold mb-4 text-center">Backtesting Engine</h1>

    <div class="sidebar" id="sidebar">
      <button class="toggle-button" onclick="toggleSidebar()">☰</button>
      <div class="sidebar-content" id="sidebarContent">
        <div class="container bg-white rounded-lg shadow-md">
          <h1 class="text-3xl font-bold mb-4 text-center">ChatBot</h1>
          <div class="chat" id="chatContainer"></div>
          <div class="flex">
            <input
              type="text"
              id="userInput"
              placeholder="Type your message here..."
              class="outline-none"
              onkeyup="handleKeyPress(event)"
            />
            <button class="send-button" onclick="sendMessage()">Send</button>
          </div>
          <div class="typing-indicator" id="typingIndicator"></div>
        </div>
      </div>
    </div>

    <form method="post">
      <label for="cs_model">Cross-Sectional Model:</label>
      <select name="cs_model" id="cs_model">
        <option value="EW">EW</option>
        <option value="MSR">MSR</option>
        <option value="GMV">GMV</option>
        <option value="MDP">MDP</option>
        <option value="EMV">EMV</option>
        <option value="RP">RP</option></select
      ><br /><br />
      <label for="ts_model">Time-Series Model:</label>
      <select name="ts_model" id="ts_model">
        <option value="VT">VT</option>
        <option value="CVT">CVT</option>
        <option value="KL">KL</option>
        <option value="CPPI">CPPI</option>
        <option value="">None</option></select
      ><br /><br />
      <button type="submit">Run Backtest</button>
    </form>
    {% if plot_url1 and plot_url2 and plot_url3 %}
    <h2>Backtesting Results</h2>
    <img src="data:image/png;base64,{{ plot_url1 }}" alt="Portfolio Weights" />
    <img
      src="data:image/png;base64,{{ plot_url2 }}"
      alt="Underlying Asset Performance"
    />
    <img
      src="data:image/png;base64,{{ plot_url3 }}"
      alt="Portfolio Performance"
    />
    {% endif %}

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

app.py

from flask import Flask, render_template, request
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
import quantstats as qs
from scipy.optimize import minimize
from scipy.stats import norm
from io import BytesIO
import base64

app = Flask(__name__)

# Function to get ETF price data
def get_etf_price_data():
    tickers = ['AAPL', 'MSFT', 'NVDA', 'GOOG', 'XLK']
    etf = yf.Tickers(tickers)
    data = etf.history(start='2010-01-01', actions=False)
    data.drop(['Open', 'High', 'Low', 'Volume'], inplace=True, axis=1)
    data = data.droplevel(0, axis=1)
    data.ffill(inplace=True)
    df = data.resample('W').last()
    return df

df = get_etf_price_data()

# Portfolio Backtesting Engine Class
class GEMTU772:
    # Initialization Function
    def __init__(self, price, param=52):
    
        # Annualization Parameter    
        self.param = param
    
        # Intraday Return Rate 
        self.rets = price.pct_change().dropna()
      
        # Expected Rate of Return        
        self.er = np.array(self.rets * self.param)
      
        # Volatility        
        self.vol = np.array(self.rets.rolling(self.param).std() * np.sqrt(self.param))
      
        # Covariance Matrix   
        cov = self.rets.rolling(self.param).cov().dropna() * self.param
      
        # Transaction Cost per Unit 
        self.cov = cov.values.reshape(int(cov.shape[0]/cov.shape[1]), cov.shape[1], cov.shape[1])
        
        self.cost = 0.0005
   
    # Cross-Sectional Risk Models Class 
    class CrossSectional:
        #EW
        def ew(self, er):
            noa = er.shape[0]
            weights = np.ones_like(er) * (1/noa)
            return weights
        
        def msr(self, er, cov):
            noa = er.shape[0] 
            init_guess = np.repeat(1/noa, noa)
            bounds = ((0.0, 1.0), ) * noa
            weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}

            
            def neg_sharpe(weights, er, cov):
                r = weights.T @ er # @ means multiplication
                vol = np.sqrt(weights.T @ cov @ weights)
                return - r / vol

            weights = minimize(neg_sharpe, init_guess, args=(er, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
            return weights.x
        
        #GMV
        def gmv(self, cov):
            noa = cov.shape[0]
            init_guess = np.repeat(1/noa, noa)
            bounds = ((0.0, 1.0), ) * noa
            weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}

            def port_vol(weights, cov):
                vol = np.sqrt(weights.T @ cov @ weights)
                return vol

            weights = minimize(port_vol, init_guess, args=(cov,), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
            return weights.x
        #MDP
        def mdp(self, vol, cov):
            noa = vol.shape[0]
            init_guess = np.repeat(1/noa, noa)
            bounds = ((0.0, 1.0), ) * noa
            weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}

            def neg_div_ratio(weights, vol, cov):
                weighted_vol = weights.T @ vol
                port_vol = np.sqrt(weights.T @ cov @ weights)
                return - weighted_vol / port_vol

            weights = minimize(neg_div_ratio, init_guess, args=(vol, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
            return weights.x
        #RP
        def rp(self, cov):
            noa = cov.shape[0]
            init_guess = np.repeat(1/noa, noa)
            bounds = ((0.0, 1.0), ) * noa
            target_risk = np.repeat(1/noa, noa)
            weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}

            def msd_risk(weights, target_risk, cov):
                port_var = weights.T @ cov @ weights
                marginal_contribs = cov @ weights
                risk_contribs = np.multiply(marginal_contribs, weights.T) / port_var
                w_contribs = risk_contribs
                return ((w_contribs - target_risk)**2).sum()

            weights = minimize(msd_risk, init_guess, args=(target_risk, cov), method='SLSQP', constraints=(weights_sum_to_1,), bounds=bounds)
            return weights.x
        #EMV
        def emv(self, vol):
            inv_vol = 1 / vol
            weights = inv_vol / inv_vol.sum()
            return weights
   
    # Time-Series Risk Models Class
    class TimeSeries:
        #VT
        def vt(self, port_rets, param, vol_target=0.1):
            vol = port_rets.rolling(param).std().fillna(0) * np.sqrt(param)
            weights = (vol_target / vol).replace([np.inf, -np.inf], 0).shift(1).fillna(0)
            weights[weights > 1] = 1
            return weights
        #CVT
        def cvt(self, port_rets, param, delta=0.01, cvar_target=0.05):
            def calculate_CVaR(rets, delta=0.01):
                VaR = rets.quantile(delta)
                return rets[rets <= VaR].mean()

            rolling_CVaR = -port_rets.rolling(param).apply(calculate_CVaR, args=(delta,)).fillna(0)
            weights = (cvar_target / rolling_CVaR).replace([np.inf, -np.inf], 0).shift(1).fillna(0)
            weights[weights > 1] = 1
            return weights
        #KL
        def kl(self, port_rets, param):
            sharpe_ratio = (port_rets.rolling(param).mean() * np.sqrt(param) / port_rets.rolling(param).std())
            weights = pd.Series(2 * norm.cdf(sharpe_ratio) - 1, index=port_rets.index).fillna(0)
            weights[weights < 0] = 0
            weights = weights.shift(1).fillna(0)
            return weights
        #CPPI
        def cppi(self, port_rets, m=3, floor=0.7, init_val=1):
            n_steps = len(port_rets)
            port_value = init_val
            floor_value = init_val * floor
            peak = init_val

            port_history = pd.Series(dtype=np.float64).reindex_like(port_rets)
            weight_history = pd.Series(dtype=np.float64).reindex_like(port_rets)
            floor_history = pd.Series(dtype=np.float64).reindex_like(port_rets)

            for step in range(n_steps):
                peak = np.maximum(peak, port_value)
                floor_value = peak * floor

                cushion = (port_value - floor_value) / port_value
                weight = m * cushion

                risky_alloc = port_value * weight
                safe_alloc = port_value * (1 - weight)
                port_value = risky_alloc * (1 + port_rets.iloc[step]) + safe_alloc

                port_history.iloc[step] = port_value
                weight_history.iloc[step] = weight
                floor_history.iloc[step] = floor_value

            return weight_history.shift(1).fillna(0)
   
    # Transaction Cost Function (Compound rate of return method assuming reinvestment)
    def transaction_cost(self, weights_df, rets_df, cost=0.0005):
        prev_weights_df = (weights_df.shift(1).fillna(0) * (1 + rets_df.iloc[self.param-1:,:])) \
            .div((weights_df.shift(1).fillna(0) * (1 + rets_df.iloc[self.param-1:,:])).sum(axis=1), axis=0)
       
        # Investment Weight of Previous Period (The backslash ('\') in Python is used as a line continuation character.)
        cost_df = abs(weights_df - prev_weights_df) * cost
        cost_df.fillna(0, inplace=True)
        return cost_df
  
    # Backtesting Execution Function
    def run(self, cs_model, ts_model, cost):
        
        # Empty Dictionary   
        backtest_dict = {}
        
        # Intraday Return Rate DataFrame
        rets = self.rets
      
        # Select and Run Cross-Sectional Risk Models
        for i, index in enumerate(rets.index[self.param-1:]):
            if cs_model == 'EW':
                backtest_dict[index] = self.CrossSectional().ew(self.er[i])
            elif cs_model == 'MSR':
                backtest_dict[index] = self.CrossSectional().msr(self.er[i], self.cov[i])
            elif cs_model == 'GMV':
                backtest_dict[index] = self.CrossSectional().gmv(self.cov[i])
            elif cs_model == 'MDP':
                backtest_dict[index] = self.CrossSectional().mdp(self.vol[i], self.cov[i])
            elif cs_model == 'EMV':
                backtest_dict[index] = self.CrossSectional().emv(self.vol[i])
            elif cs_model == 'RP':
                backtest_dict[index] = self.CrossSectional().rp(self.cov[i])
     
        # Cross-Sectional Weights DataFrame
        cs_weights = pd.DataFrame(list(backtest_dict.values()), index=backtest_dict.keys(), columns=rets.columns)   
        cs_weights.fillna(0, inplace=True)
        
         # Cross-Sectional Risk Models Return on Assets
        cs_rets = cs_weights.shift(1) * rets.iloc[self.param-1:,:]
       
        # Cross-Sectional Risk Models Portfolio Return
        cs_port_rets = cs_rets.sum(axis=1)
        
        # Select and Run Time-Series Risk Models
        if ts_model == 'VT':
            ts_weights = self.TimeSeries().vt(cs_port_rets, self.param)
        elif ts_model == 'CVT':
            ts_weights = self.TimeSeries().cvt(cs_port_rets, self.param)
        elif ts_model == 'KL':
            ts_weights = self.TimeSeries().kl(cs_port_rets, self.param)
        elif ts_model == 'CPPI':
            ts_weights = self.TimeSeries().cppi(cs_port_rets)
        elif ts_model == None:
            ts_weights = 1
            
        # Final Portfolio Investment Weights
        port_weights = cs_weights.multiply(ts_weights, axis=0)
        
        # Transaction Cost DataFrame
        cost = self.transaction_cost(port_weights, rets)
        
        # Final Portfolio Return by Assets
        port_asset_rets = port_weights.shift() * rets - cost
        
        # Final Portfolio Return
        port_rets = port_asset_rets.sum(axis=1)
        port_rets.index = pd.to_datetime(port_rets.index).strftime("%Y-%m-%d")

        return port_weights, port_asset_rets, port_rets

    def performance_analytics(self, port_weights, port_asset_rets, port_rets):
        img = BytesIO()
        
        plt.figure(figsize=(12, 7))
        port_weights['Cash'] = 1 - port_weights.sum(axis=1)
        plt.stackplot(port_weights.index, port_weights.T, labels=port_weights.columns)
        plt.title('Portfolio Weights')
        plt.xlabel('Date')
        plt.ylabel('Weights')
        plt.legend(loc='upper left')
        plt.savefig(img, format='png')
        plt.close()
        img.seek(0)
        plot_url1 = base64.b64encode(img.getvalue()).decode('utf8')

        img = BytesIO()
        plt.figure(figsize=(12, 7))
        plt.plot((1 + port_asset_rets).cumprod() - 1)
        plt.title('Underlying Asset Performance')
        plt.xlabel('Date')
        plt.ylabel('Returns')
        plt.legend(port_asset_rets.columns, loc='upper left')
        plt.savefig(img, format='png')
        plt.close()
        img.seek(0)
        plot_url2 = base64.b64encode(img.getvalue()).decode('utf8')

        img = BytesIO()
        plt.figure(figsize=(12, 7))
        plt.plot((1 + port_rets).cumprod() - 1)
        plt.title('Portfolio Performance')
        plt.xlabel('Date')
        plt.ylabel('Returns')
        plt.savefig(img, format='png')
        plt.close()
        img.seek(0)
        plot_url3 = base64.b64encode(img.getvalue()).decode('utf8')

        return plot_url1, plot_url2, plot_url3

#Route function 
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        cs_model = request.form.get('cs_model') # cs model selection
        ts_model = request.form.get('ts_model') # ts model selection
        engine = GEMTU772(df)  # Run backtesting
        res = engine.run(cs_model=cs_model, ts_model=ts_model, cost=0.0005) # Run method
        port_weights = res[0]
        port_asset_rets = res[1]
        port_rets = res[2]
        plot_url1, plot_url2, plot_url3 = engine.performance_analytics(port_weights, port_asset_rets, port_rets)
        return render_template('index.html', plot_url1=plot_url1, plot_url2=plot_url2, plot_url3=plot_url3) # Rendering
    return render_template('index.html')    
    
    

if __name__ == '__main__':
    app.run(debug=True)

Solution

  • Looks to me like you are able to type in the input. Only issue is you input is not visible due to color. just adding color to input[type="text"] css should make it visible.

    input[type="text"] {
    color: black;
    }
    

    const promptInput = document.getElementById("userInput");
    const chatContainer = document.getElementById("chatContainer");
    const typingIndicator = document.getElementById("typingIndicator");
    const sidebar = document.getElementById("sidebar");
    const sidebarContent = document.getElementById("sidebarContent");
    
    async function sendMessage() {
      const prompt = promptInput.value.trim();
      if (!prompt) {
        alert("Please enter a message.");
        return;
      }
    
      addMessage(prompt, 'user');
      promptInput.value = "";
    
      showTypingIndicator();
    
      const generatedText = await generateText(prompt);
      addMessage(generatedText, 'bot');
    
      hideTypingIndicator();
    }
    
    async function generateText(prompt) {
      try {
        const response = await fetch("http://127.0.0.1:5000/generate_text_stream", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ prompt }),
        });
    
        if (!response.ok) {
          console.error("Error:", response.statusText);
          return "Error occurred while generating response.";
        }
    
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let isFinished = false;
        let generatedTextContent = "";
    
        while (!isFinished) {
          const { done, value } = await reader.read();
          if (done) {
            isFinished = true;
            break;
          }
          generatedTextContent += decoder.decode(value, {stream: true});
        }
    
        return generatedTextContent;
      } catch (error) {
        console.error("Error:", error);
        return "An error occurred.";
      }
    }
    
    function addMessage(text, type) {
      const messageDiv = document.createElement("div");
      messageDiv.className = `message ${type}`;
      messageDiv.innerHTML = `<div class="message-bubble fadeIn">${text}</div>`;
      chatContainer.appendChild(messageDiv);
    
      chatContainer.scrollTop = chatContainer.scrollHeight;
    
      hideTypingIndicator();
    }
    
    let typingTimeout;
    
    function showTypingIndicator() {
      clearTimeout(typingTimeout);
      typingIndicator.style.display = "inline-block";
    }
    
    function hideTypingIndicator() {
      typingTimeout = setTimeout(() => {
          typingIndicator.style.display = "none";
      }, 1000);
    }
    
    function handleKeyPress(event) {
      if (event.key === "Enter") {
          sendMessage();
      }
    }
    
    function toggleSidebar() {
      if (sidebar.style.width === "500px") {
        sidebar.style.width = "0";
        sidebarContent.style.display = "none";
      } else {
        sidebar.style.width = "500px";
        sidebarContent.style.display = "block";
      }
    }
    
    window.onload = () => addMessage("Hello! How can I assist you today?", 'bot');
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        text-align: center;
        background-color: #f4f4f4;
    }
    
    
    
    .container {
        margin-top: 0;
        width: 90%;
        max-width: 450px;
        margin: 10px auto 0;
        background-color: #fff;
        border-radius: 12px;
        box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
        padding: 20px;
        transition: all 0.3s;
    }
    
    .chat {
        overflow-y: auto;
        height: 400px;
        margin-bottom: 20px;
        border-bottom: 2px solid #e2e2e2;
    }
    
    .message {
        display: flex;
        margin-bottom: 12px;
    }
    
    .message.user {
        justify-content: flex-end;
    }
    
    .message-bubble {
        padding: 12px 18px;
        max-width: 70%;
        border-radius: 20px;
        line-height: 1.6;
        font-size: 0.95rem;
    }
    
    .message.user .message-bubble {
        background-color: #3182ce;
        color: white;
    }
    
    .message.bot .message-bubble {
        background-color: #e2e2e2;
        color: #333;
    }
    
    input[type="text"] {
        width: calc(100% - 110px);
        padding: 12px 18px;
        border: 2px solid #e2e2e2;
        border-radius: 8px 0 0 8px;
        font-size: 1rem;
        outline: none;
        color: black;
        z-index: 2; /* Ensure it's above other elements */
    }
    
    .send-button {
        width: 110px;
        background-color: #3182ce;
        color: white;
        padding: 12px 18px;
        border: none;
        border-radius: 0 8px 8px 0;
        cursor: pointer;
        transition: background-color 0.3s;
    }
    
    .send-button:hover {
        background-color: #2c5282;
    }
    
    .footer {
        text-align: center;
        padding: 15px 0;
        font-size: 0.9rem;
        color: #666;
        position: static;
        border-top: 1px solid #e2e2e2;
        background-color: #fff;
        position: fixed;
        bottom: 0;
        width: 100%;
    }
    
    @keyframes fadeIn {
        from {
            opacity: 0;
        }
    
        to {
            opacity: 1;
        }
    }
    
    .fadeIn {
        animation: fadeIn 1s;
    }
    
    @media (max-width: 600px) {
        .container {
            width: 95%;
            margin: 10px auto 0;
        }
    
        .chat {
            height: 300px;
        }
    
        input[type="text"],
        .send-button {
            padding: 10px 14px;
            font-size: 0.9rem;
        }
    
        .footer {
            font-size: 0.8rem;
            margin-top: 30px;
        }
    }
    
    .typing-indicator {
        display: none;
        align-items: center;
        justify-content: flex-end;
        margin-top: 8px;
        width: 10px;
        height: 10px;
        background-color: #333;
        border-radius: 50%;
        margin-left: 4px;
        animation: typing 1s infinite;
    }
    
    @keyframes typing {
        0%,
        100% {
            transform: scale(1);
            opacity: 1;
        }
    
        50% {
            transform: scale(1.2);
            opacity: 0.7;
        }
    }
    
    /* Sidebar Styles */
    .sidebar {
        position: fixed;
        right: 0;
        top: 0;
        height: 100%;
        width: 100%;
        max-width: 500px;
        background-color: #f4f4f4;
        overflow-x: hidden;
        transition: 0.5s;
        padding-top: 60px;
        color: white;
    }
    
    .sidebar-content {
        display: none;
    }
    
    .sidebar-content h2 {
        text-align: center;
    }
    
    .sidebar-content p {
        padding: 10px;
    }
    
    .toggle-button {
        position: fixed;
        right: 0;
        top: 0;
        padding: 15px;
        background-color: #3182ce;
        color: white;
        border: none;
        cursor: pointer;
    }
    
    .toggle-button:hover {
        background-color: #2c5282;
    }
    
    
    
    
    
    h1 {
        color: rgb(0, 0, 0);
        padding: 20px 0;
        margin: 0 0 20px 0;
    }
    
    form {
        display: inline-block;
        background-color: #fff;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
    
    label {
        display: block;
        margin-bottom: 10px;
        font-weight: bold;
    }
    
    select {
        width: 100%;
        padding: 10px;
        margin-bottom: 20px;
        border-radius: 5px;
        border: 1px solid #ccc;
    }
    
    button {
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        background-color: #4CAF50;
        color: white;
        font-size: 16px;
        cursor: pointer;
    }
    
    button:hover {
        background-color: #45a049;
    }
    
    img {
        max-width: 90%;
        margin: 20px 0;
        border: 1px solid #ddd;
        border-radius: 10px;
    }
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Backtesting Engine</title>
        <link
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
          rel="stylesheet"
        />
        <link
          rel="stylesheet"
          href="{{ url_for('static', filename='css/style.css') }}"
        />
      </head>
      <body>
        <h1 class="text-6xl font-bold mb-4 text-center">Backtesting Engine</h1>
    
        <div class="sidebar" id="sidebar">
          <button class="toggle-button" onclick="toggleSidebar()">☰</button>
          <div class="sidebar-content" id="sidebarContent">
            <div class="container bg-white rounded-lg shadow-md">
              <h1 class="text-3xl font-bold mb-4 text-center">ChatBot</h1>
              <div class="chat" id="chatContainer"></div>
              <div class="flex">
                <input
                  type="text"
                  id="userInput"
                  placeholder="Type your message here..."
                  class="outline-none"
                  onkeyup="handleKeyPress(event)"
                />
                <button class="send-button" onclick="sendMessage()">Send</button>
              </div>
              <div class="typing-indicator" id="typingIndicator"></div>
            </div>
          </div>
        </div>
    
        <form method="post">
          <label for="cs_model">Cross-Sectional Model:</label>
          <select name="cs_model" id="cs_model">
            <option value="EW">EW</option>
            <option value="MSR">MSR</option>
            <option value="GMV">GMV</option>
            <option value="MDP">MDP</option>
            <option value="EMV">EMV</option>
            <option value="RP">RP</option></select
          ><br /><br />
          <label for="ts_model">Time-Series Model:</label>
          <select name="ts_model" id="ts_model">
            <option value="VT">VT</option>
            <option value="CVT">CVT</option>
            <option value="KL">KL</option>
            <option value="CPPI">CPPI</option>
            <option value="">None</option></select
          ><br /><br />
          <button type="submit">Run Backtest</button>
        </form>
        {% if plot_url1 and plot_url2 and plot_url3 %}
        <h2>Backtesting Results</h2>
        <img src="data:image/png;base64,{{ plot_url1 }}" alt="Portfolio Weights" />
        <img
          src="data:image/png;base64,{{ plot_url2 }}"
          alt="Underlying Asset Performance"
        />
        <img
          src="data:image/png;base64,{{ plot_url3 }}"
          alt="Portfolio Performance"
        />
        {% endif %}
    
        <script src="{{ url_for('static', filename='js/script.js') }}"></script>
      </body>
    </html>

    Hope this helps! If not let me know what i missed. Do accept the answer if it helps.