I am developing a website that has a button on the top right side and if the user clicks this button, the sidebar with a chatbot shows up. I want to make this chatbot sidebar resizable by using the resize property to allow the user to resize the sidebar with their mouse.
Even though I have added the resize property to .sidebar-content
, I can't resize the chatbot sidebar.
/* Side bar */
.sidebar {
position: fixed;
top: 10px;
right: 10px;
height: 100%;
width: 100%;
max-width: 500px;
background-color: none;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
color: white;
}
.sidebar-content {
resize: both;
display: none;
}
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");
const imageContainer = document.getElementById("imageContainer");
async function sendMessage() {
const prompt = promptInput.value.trim();
if (!prompt && imageContainer.children.length === 0) {
alert("Please enter a message or add an image."); // Browser pop up message
return;
}
// Collect image data
const images = Array.from(imageContainer.querySelectorAll('.img-preview'))
.map(img => img.src.split(',')[1]); // Extract base64 data
addMessage(prompt, 'user', images);
promptInput.value = "";
showTypingIndicator();
const generatedText = await generateText(prompt, images);
addMessage(generatedText, 'bot');
hideTypingIndicator();
//clearImagePreviews(); Add this code if you want the image in the imageContainer disappear if the user sends the image.
}
async function generateText(prompt, images) {
try {
const response = await fetch("https://idx-polapo-invest-test-6163648-nylaufaarq-uc.a.run.app/generate_text_stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt, images }),
});
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, images = []) {
let md = window.markdownit();
const messageDiv = document.createElement("div");
messageDiv.className = `message ${type}`;
const messageContent = document.createElement("div");
messageContent.className = "message-bubble fadeIn";
const render = () => {
messageContent.innerHTML = md.render(text);
}
render()
images.forEach(src => {
const img = document.createElement("img");
img.src = `data:image/png;base64,${src}`;
img.classList.add("message-image");
messageContent.appendChild(img);
});
messageDiv.appendChild(messageContent);
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function clearImagePreviews() {
while (imageContainer.firstChild) {
imageContainer.removeChild(imageContainer.firstChild);
}
checkImageContainerVisibility();
}
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');
document.addEventListener('DOMContentLoaded', () => {
const textInput = document.getElementById('userInput');
textInput.addEventListener('paste', (event) => {
const items = (event.clipboardData || window.clipboardData).items;
for (const item of items) {
if (item.type.indexOf('image') !== -1) {
const file = item.getAsFile();
const reader = new FileReader();
reader.onload = (event) => {
displayImage(event.target.result);
};
reader.readAsDataURL(file);
event.preventDefault();
}
}
});
function displayImage(src) {
const imgContainer = document.createElement('div');
imgContainer.classList.add('img-preview-container');
const img = document.createElement('img');
img.src = src;
img.classList.add('img-preview');
const removeButton = document.createElement('button');
removeButton.classList.add('remove-button');
removeButton.textContent = '✖';
removeButton.addEventListener('click', () => {
imgContainer.remove();
checkImageContainerVisibility();
});
imgContainer.appendChild(img);
imgContainer.appendChild(removeButton);
imageContainer.appendChild(imgContainer);
checkImageContainerVisibility();
const all_images = imageContainer.querySelectorAll('.img-preview-container');
all_images.forEach(img => img.style.width = `${100 / all_images.length - 10}%`);
}
function checkImageContainerVisibility() {
if (imageContainer.children.length > 0) {
imageContainer.classList.remove('hidden');
} else {
imageContainer.classList.add('hidden');
}
}
// Initial check to hide image container if empty
checkImageContainerVisibility();
});
// Backtest Result and Detailed Report buttons
document.getElementById('backtestResultButton').addEventListener('click', function() {
const cs_model = document.getElementById('cs_model').value;
const ts_model = document.getElementById('ts_model').value;
fetch('/Backtest_result', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Error:', data.error);
return;
}
document.getElementById('backtestResult').innerHTML = `
<div class="image" id="image1">
<img src="data:image/png;base64,${data.port_weights_img}" alt="Portfolio Weights" style="width: 80%; height: auto;">
</div>
<div class="image" id="image2">
<img src="data:image/png;base64,${data.asset_performance_img}" alt="Asset Performance" style="width: 80%; height: auto;">
</div>
<div class="image" id="image3">
<img src="data:image/png;base64,${data.portfolio_performance_img}" alt="Portfolio Performance" style="width: 80%; height: auto;">
</div>
`;
document.getElementById('backtestResult').classList.add('active');
document.getElementById('detailedReport').classList.remove('active');
})
.catch(error => {
console.error('Error:', error);
});
});
document.getElementById('detailedReportButton').addEventListener('click', function() {
const cs_model = document.getElementById('cs_model').value;
const ts_model = document.getElementById('ts_model').value;
fetch('/generate_html_report', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Error:', data.error);
return;
}
document.getElementById('detailedReport').innerHTML = data.report_html;
document.getElementById('detailedReport').classList.add('active');
document.getElementById('backtestResult').classList.remove('active');
})
.catch(error => {
console.error('Error:', error);
});
});
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f2f5;
color: #333;
margin: 0;
padding: 0;
text-align: center;
}
h1 {
color: #333;
text-align: center;
margin: 20px 0;
}
/* Generate report button styling */
button, input[type="submit"] {
padding: 12px 24px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s, transform 0.3s;
}
button:hover, input[type="submit"]:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
/* Form styling */
form {
background: #fff;
border-radius: 8px;
padding: 20px;
max-width: 600px;
margin: 20px auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
font-size: 16px;
}
select, input[type="submit"] {
width: calc(100% - 22px);
padding: 12px;
margin-bottom: 20px;
border-radius: 6px;
border: 1px solid #ddd;
font-size: 16px;
}
select {
background-color: #f9f9f9;
}
/* Report page styling */
.report-container {
background: #fff;
border-radius: 8px;
padding: 20px;
max-width: 1200px;
margin: 20px auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: auto;
}
a {
color: #007bff;
text-decoration: none;
font-weight: 500;
}
a:hover {
text-decoration: underline;
}
/* Section that will show report content */
.report-content {
margin-top: 20px;
}
.report-content iframe {
width: 100%;
border: none;
height: 600px;
}
/* Side bar */
.sidebar {
position: fixed;
top: 10px;
right: 10px;
height: 100%;
width: 100%;
max-width: 500px;
background-color: none;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
color: white;
}
.sidebar-content {
resize: both;
display: none;
}
.sidebar-content h2 {
text-align: center;
}
.sidebar-content p {
padding: 10px;
}
.toggle-button {
position: fixed;
top: 10px;
right: 10px;
padding: 15px;
background-color: #3182ce;
color: white;
border: none;
cursor: pointer;
}
.toggle-button:hover {
background-color: #2c5282;
}
img {
max-width: 90%;
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 10px;
}
.image-container {
width: 100%;
/*padding: 12px 18px;*/
overflow: hidden;
display: flex;
flex-wrap: wrap;
gap: 10px;
background-color: #fff;
/*border: 1px solid #444;*/
min-height: 50px;
max-height: 70px;
margin-bottom: 10px;
border: 2px solid #e2e2e2;
border-radius: 8px 8px 8px 8px;
}
.hidden {
display: none;
}
.img-preview-container {
position: relative;
display: inline-block;
max-width: 15%;
}
.img-preview {
max-width: 100%;
border-radius: 5px;
}
.remove-button:hover {
background-color: #45a049;
}
.remove-button {
position: absolute;
top: 5px;
right: 5px;
background-color: #ff4d4d;
border: none;
border-radius: 50%;
color: white;
cursor: pointer;
width: 20px;
height: 20px;
font-size: 12px;
line-height: 20px;
text-align: center;
padding: 0;
}
/* Chat Bot */
.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;
}
.message-image {
max-width: 100px; /* Set the maximum width for the image */
max-height: 100px; /* Set the maximum height for the image */
margin: 5px;
display: inline-block;
object-fit: cover; /* Ensures the image retains its aspect ratio */
}
input[type="text"] {
flex: 1;
padding: 12px 18px;
border: 2px solid #e2e2e2;
border-radius: 8px 0 0 8px;
font-size: 1rem;
outline: none;
color: black;
}
.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-container {
max-width: 95%;
}
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;
}
}
/*the backtest result and detailed report buttons */
.button-container {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px 0;
}
.content {
display: flex;
justify-content: center;
margin-top: 20px;
}
.content-box {
display: none;
flex-direction: row;
}
.content-box.active {
display: flex;
}
/*Backtest_result style*/
.image img {
width: auto;
height: auto;
margin: 10px auto;
display: block;
cursor: pointer;
}
#detailedReport {
width: 90%;
margin: 0 auto;
}
/*image modal style*/
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.9);
}
.modal-content {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
}
.modal-content, #caption {
-webkit-animation-name: zoom;
-webkit-animation-duration: 0.6s;
animation-name: zoom;
animation-duration: 0.6s;
}
@-webkit-keyframes zoom {
from { -webkit-transform: scale(0) }
to { -webkit-transform: scale(1) }
}
@keyframes zoom {
from { transform: scale(0) }
to { transform: scale(1) }
}
.close {
position: absolute;
top: 50px;
right: 50px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
transition: 0.3s;
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Portfolio Backtesting</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
</head>
<body>
<h1>Portfolio Backtesting</h1>
<form id="reportForm" method="post" action="/">
<label for="cs_model">Choose Cross-Sectional Model:</label>
<select id="cs_model" name="cs_model">
<option value="EW">Equal Weight (EW)</option>
<option value="MSR">Maximum Sharpe Ratio (MSR)</option>
<option value="GMV">Global Minimum Variance (GMV)</option>
<option value="MDP">Minimum Drawdown Risk (MDP)</option>
<option value="EMV">Equal Risk Contribution (EMV)</option>
<option value="RP">Risk Parity (RP)</option>
</select>
<label for="ts_model">Choose Time-Series Model:</label>
<select id="ts_model" name="ts_model">
<option value="VT">Volatility Targeting (VT)</option>
<option value="CVT">Conditional Value at Risk Targeting (CVT)</option>
<option value="KL">Kelly Criterion (KL)</option>
<option value="CPPI">
Constant Proportion Portfolio Insurance (CPPI)
</option>
</select>
<button type="submit" class="button">Generate Report</button>
</form>
<div class="button-container">
<button type="button" id="backtestResultButton" class="button">
Backtest Result
</button>
<button type="button" id="detailedReportButton" class="button">
Detailed Report
</button>
</div>
<div class="content">
<div id="backtestResult" class="content-box"></div>
<div id="detailedReport" class="content-box"></div>
</div>
<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="image-container hidden" id="imageContainer"></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>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/markdown-it.min.js"></script>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<!-- image moddal -->
<div id="myModal" class="modal">
<span class="close">×</span>
<img class="modal-content" id="img01" />
<div id="caption"></div>
</div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<script>
document
.getElementById("backtestResultButton")
.addEventListener("click", function () {
const cs_model = document.getElementById("cs_model").value;
const ts_model = document.getElementById("ts_model").value;
fetch("/Backtest_result", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
})
.then((response) => response.json())
.then((data) => {
if (data.error) {
console.error("Error:", data.error);
return;
}
document.getElementById("backtestResult").innerHTML = `
<div class="image" id="image1">
<img src="data:image/png;base64,${data.port_weights_img}" alt="Portfolio Weights">
</div>
<div class="image" id="image2">
<img src="data:image/png;base64,${data.asset_performance_img}" alt="Asset Performance">
</div>
<div class="image" id="image3">
<img src="data:image/png;base64,${data.portfolio_performance_img}" alt="Portfolio Performance">
</div>
`;
document.getElementById("backtestResult").classList.add("active");
document
.getElementById("detailedReport")
.classList.remove("active");
// image click event
const images = document.querySelectorAll(".image img");
images.forEach((img) => {
img.addEventListener("click", function () {
const modal = document.getElementById("myModal");
const modalImg = document.getElementById("img01");
const captionText = document.getElementById("caption");
modal.style.display = "block";
modalImg.src = this.src;
captionText.innerHTML = this.alt;
});
});
})
.catch((error) => {
console.error("Error:", error);
});
});
document
.getElementById("detailedReportButton")
.addEventListener("click", function () {
const cs_model = document.getElementById("cs_model").value;
const ts_model = document.getElementById("ts_model").value;
fetch("/generate_html_report", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ cs_model: cs_model, ts_model: ts_model }),
})
.then((response) => response.json())
.then((data) => {
if (data.error) {
console.error("Error:", data.error);
return;
}
document.getElementById("detailedReport").innerHTML =
data.report_html;
document.getElementById("detailedReport").classList.add("active");
document
.getElementById("backtestResult")
.classList.remove("active");
})
.catch((error) => {
console.error("Error:", error);
});
});
// modal close
const modal = document.getElementById("myModal");
const span = document.getElementsByClassName("close")[0];
span.onclick = function () {
modal.style.display = "none";
};
</script>
</body>
</html>
try adding
overflow: auto;
.sidebar-content {
resize: both;
overflow: auto;
display: none;
}
and don't forget to specify max-width/min-width
and max-height/min-height
if required.