JSFiddle - React, Tailwind, and code Playground
by Ed Fabre
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtual Assistant</title>
<!-- Include Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<script>
window.messagesFromServer = {{ messages | tojson }};
</script>
</head>
<body>
<div id="app" class="container mt-5">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" :class="{ active: currentTab === 'chat' }" href="#" @click="showTab('chat')">Chat</a>
</li>
<li class="nav-item">
<a class="nav-link" :class="{ active: currentTab === 'dailyreports' }" href="#" @click="showTab('dailyreports')">Daily Reports</a>
</li>
</ul>
<div v-if="currentTab === 'chat'">
<div class="chatbox" ref="chatbox">
{% raw %}
<div v-for="message in messages" :class="message.role">
<p v-html="markdownToHtml(message.content)"></p>
</div>
{% endraw %}
</div>
<div class="input-group mt-3">
<input type="text" v-model="userInput" class="form-control" placeholder="Type your message..." @keyup.enter="sendMessage">
<button @click="sendMessage" class="btn btn-primary">Send</button>
</div>
</div>
<div v-if="currentTab === 'dailyreports'">
<h2>Daily Reports</h2>
<!-- Add & Generate Report Buttons -->
<div class="mb-3">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addReportModal">Add Report</button>
<button class="btn btn-secondary" data-bs-toggle="modal"...
CSS
.chatbox {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
background-color: #f5f5f5;
}
.user, .assistant {
max-width: 70%;
padding: 10px;
border-radius: 15px;
margin-bottom: 10px;
display: inline-block;
}
.user {
background-color: #007bff;
color: white;
float: right;
clear: both;
}
.assistant {
background-color: white;
color: black;
float: left;
clear: both;
border: 1px solid #ccc;
}
.loader {
border: 5px solid #f3f3f3;
border-radius: 50%;
border-top: 5px solid blue;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
Vue
// Initialize the markdown-it library
const md = window.markdownit();
new Vue({
el: "#app",
data: {
messages: window.messagesFromServer || [],
userInput: "",
testMessage: "This is a test",
successMessage: null,
currentTab: "chat",
records: [],
newRecord: {
date: new Date().toISOString().substr(0, 10),
content: "",
},
editingRecord: {
date: "",
content: "",
},
userEnteredDateRange: {
startDate: "",
endDate: "",
},
generatedReport: "",
isEditing: false,
isLoading: false,
filterQuery: "",
currentPage: 1,
perPage: 10,
},
computed: {
filteredRecords() {
if (this.filterQuery) {
return this.records.filter((record) =>
record.content.toLowerCase().includes(this.filterQuery.toLowerCase())
);
}
console.log("filteredRecords:", this.records);
return this.records;
},
paginatedRecords() {
let start = (this.currentPage - 1) * this.perPage;
let end = start + this.perPage;
return this.filteredRecords.slice(start, end);
},
totalPages() {
return Math.ceil(this.filteredRecords.length / this.perPage);
},
},
created: function () {
console.log("Vue instance created");
console.log(this.testMessage);
// After adding a new message, scroll the chatbox to the bottom
this.$nextTick(() => {
const chatbox = this.$refs.chatbox;
chatbox.scrollTop = chatbox.scrollHeight;
});
},
methods: {
showPage(pageNumber) {
this.currentPage = pageNumber;
},
sendMessage: function () {
this.messages.push({ role: "user", content: this.userInput });
fetch("/ask", {
method: "POST",
body: new URLSearchParams(`user_input=${this.userInput}`),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((response) => {
if (!response.ok) {
throw new...