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...