Skip to content

Commit

Permalink
Merge pull request #82 from priyanshuverma-dev/feat-anon-chat
Browse files Browse the repository at this point in the history
fixes #78: Feature a anonymous chat bot
  • Loading branch information
kom-senapati authored Oct 11, 2024
2 parents cc324ba + 7361d90 commit af3df70
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 1 deletion.
51 changes: 50 additions & 1 deletion api_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
redirect,
url_for,
jsonify,
session,
Response,
)
import json
from models import User, Chatbot, Chat
from sqlalchemy.exc import IntegrityError
from flask_login import login_user, current_user, login_required
from typing import Union, List, Optional, Dict
from ai import chat_with_chatbot
from constants import BOT_AVATAR_API, USER_AVATAR_API
from datetime import datetime

api_bp = Blueprint("api", __name__)

ANONYMOUS_MESSAGE_LIMIT = 5

api_bp = Blueprint("api", __name__)
# Initialize variables for db and bcrypt
db = None
bcrypt = None
Expand Down Expand Up @@ -228,3 +233,47 @@ def api_profile_edit() -> Union[Response, tuple[Response, int]]:
jsonify({"message": "Username already exists.", "success": False}),
400,
)


@api_bp.route("/api/anonymous", methods=["POST"])
def api_anonymous_chatbot() -> Union[Response, tuple[Response, int]]:
"""API endpoint to interact with a chatbot."""

if not current_user.is_authenticated:
# Track message count for anonymous users using session
if "anonymous_message_count" not in session:
session["anonymous_message_count"] = 0
session["first_message_time"] = (
datetime.now().isoformat()
) # Store time of the first message

# Increment message count
session["anonymous_message_count"] += 1

# Check if the limit has been reached
if session["anonymous_message_count"] > ANONYMOUS_MESSAGE_LIMIT:
return (
jsonify(
{
"success": False,
"error": "Anonymous users are limited to 5 messages.",
}
),
429,
) # HTTP 429 Too Many Requests

prev_chats = json.loads(request.form["prev"])
query: str = request.form["query"]

chat_to_pass: List[Dict[str, str]] = prev_chats
chat_to_pass.append({"role": "user", "content": query})

response: Optional[str] = chat_with_chatbot(chat_to_pass)

return jsonify(
{
"success": True,
"response": response,
"updated_chats": chat_to_pass,
}
)
10 changes: 10 additions & 0 deletions routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ def chatbot(chatbot_id: int) -> Union[str, Response]:
)


@bp.route("/anonymous", methods=["GET"])
def anonymous_chatbot() -> Union[str, Response]:
"""Render the chatbot page for the specified chatbot."""
full_page: bool = request.args.get("full", "true").lower() == "true"
is_logged_in: bool = current_user.is_authenticated
return render_template(
"anonymous.html", full_page=full_page, is_logged_in=is_logged_in
)


@bp.route("/chatbot_hub")
@login_required
def chatbot_hub() -> str:
Expand Down
17 changes: 17 additions & 0 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ function navigate(event, path) {
event.preventDefault(); // Prevent the default anchor behavior
history.pushState(null, "", path); // Change the URL without reloading the page
loadContent(path); // Load the content dynamically
toggleAnonymousChatbotButton();
}

async function loadContent(path) {
Expand All @@ -13,6 +14,7 @@ async function loadContent(path) {
"/login": "/login",
"/signup": "/signup",
"/chatbot": "/chatbot",
"/anonymous": "/anonymous",
"/chatbot_hub": "/chatbot_hub",
"/create_chatbot": "/create_chatbot",
"/chatbot/:id/update": "/chatbot/{id}/update", // Placeholder for chatbot update
Expand Down Expand Up @@ -128,3 +130,18 @@ function initializeTheme() {
}
// Initialize theme on page load
initializeTheme();

// Prevent showing the button when already on the '/anonymous' route
function toggleAnonymousChatbotButton() {
const currentPath = window.location.pathname;
const button = document.getElementById("anonymousChatbotButton");
if (currentPath === "/anonymous") {
// Hide the button if the user is on the anonymous page
if (button) button.style.display = "none";
} else {
// Show the button if the user is not on the anonymous page
if (button) button.style.display = "flex";
}
}

toggleAnonymousChatbotButton();
128 changes: 128 additions & 0 deletions static/js/anonymous.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Function to retrieve previous chats in the desired format
function getPreviousChats() {
const chats = []; // Initialize an array to hold previous chats

// Select all messages (both user and assistant) that have the correct classes
const allMessages = document.querySelectorAll(
".flex.justify-end .max-w-xs, .flex.justify-start .max-w-md"
);

// Iterate through all messages
allMessages.forEach((message) => {
// Determine the role based on the message's parent class
const isUserMessage = message.classList.contains("max-w-xs");
const content = message.textContent.trim(); // Get the content of the message

// Push the chat object to the array with the correct role
chats.push({
role: isUserMessage ? "user" : "assistant",
content,
});
});

return chats; // Return the array of previous chats
}

// Function to send a message to the anonymous chatbot
async function sendAnonymousMessage(event) {
event.preventDefault(); // Prevent the default form submission

const messageForm = document.getElementById("anonymous-message-form"); // Get the form element
const formData = new FormData(messageForm); // Collect form data
const userQuery = formData.get("query");

// Get previous chats in the desired format and append them to form data
const prevChats = getPreviousChats();
formData.append("prev", JSON.stringify(prevChats));
console.log(prevChats);

try {
const updatedChats = [...prevChats, { role: "user", content: userQuery }];
const response = await fetch("/api/anonymous", {
method: "POST",
body: formData,
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "An error occurred."); // Handle non-200 responses
}

const data = await response.json(); // Assuming the server responds with JSON

if (data.success) {
// Construct the updated chats array
// Prepare the updated chats list

// Add the assistant's response
updatedChats.push({ role: "assistant", content: data.response });
updateChatUI(updatedChats); // Update the chat UI with the new messages
setTimeout(scrollToBottom, 100); // Scroll to the bottom of the chat
} else {
// Handle errors returned by the server
document.getElementById("error").textContent =
data.error || "An error occurred.";
}
} catch (error) {
console.error("Failed:", error);
document.getElementById("error").textContent = error.message; // Display error message
}
}

// Function to update the chat UI with new messages
function updateChatUI(updatedChats) {
const chatContainer = document.getElementById("chatContainer"); // Update the ID to match your container
chatContainer.innerHTML = ""; // Clear current chat

if (updatedChats.length === 0) {
// If there are no chats, show a message
chatContainer.innerHTML = `<p class="text-center text-gray-500">No messages for this chatbot yet.</p>`;
return;
}

updatedChats.forEach((chat) => {
// Append user message
if (chat.role === "user") {
// Use === for comparison
const userMessage = `
<div class="flex justify-end">
<div class="max-w-xs bg-blue-500 text-white rounded-xl p-4 drop-shadow shadow">
<p class="text-sm">${chat.content}</p>
</div>
</div>`;
chatContainer.insertAdjacentHTML("beforeend", userMessage);
}

// Append assistant response
if (chat.role === "assistant") {
// Check if the role is assistant
const assistantResponse = `
<div class="flex justify-start items-center space-x-2 mb-2">
<div class="max-w-md bg-white dark:bg-dark dark:text-dark/90 text-gray-900 rounded-xl p-4 drop-shadow-md shadow border border-gray-100 dark:border-darker flex flex-col">
<p class="text-sm chat-response flex-1">
${chat.content} <!-- Use markdown rendering if necessary -->
</p>
</div>
</div>`;
chatContainer.insertAdjacentHTML("beforeend", assistantResponse);
}
});

// Scroll to the bottom after updating the chat
scrollToBottom();
}

// Scroll to the bottom of the chat container
function scrollToBottom() {
const chatContainer = document.getElementById("chatContainer");
if (chatContainer) {
chatContainer.scrollTop = chatContainer.scrollHeight; // Scroll to the bottom
} else {
console.error("chatContainer not found");
}
}

// Event listener for the message form submission
document
.getElementById("anonymous-message-form")
.addEventListener("submit", sendAnonymousMessage);
56 changes: 56 additions & 0 deletions templates/anonymous.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% if full_page %}
{% extends 'base.html' %}
{% endif %}

<div
class="flex flex-col border-x-2 border-lighter dark:border-darker max-w-7xl mx-auto rounded-sm dark:bg-dark bg-light dark:text-dark h-screen">
<div class="flex items-center justify-between m-3">
<div class="flex items-center space-x-2">
<img src="https://robohash.org/Anonymous Chatbot" alt="Anonymous's avatar"
class="w-10 h-10 border rounded-full dark:border-darker mr-3">
<h1 class="text-4xl font-extrabold dark:text-dark text-center">Anonymous Chatbot</h1>
</div>
<a href="{% if is_logged_in %}{{ url_for('routes.dashboard') }}{% else %}{{ url_for('routes.index') }}{% endif %}"
onclick="navigate(event, '{% if is_logged_in %}/dashboard{% else %}/{% endif %}')" class="block mb-4">
<button type="button" class="shadow bg-blue-500 text-white rounded-full transition-colors hover:bg-blue-600">
<svg class="h-10 w-10 p-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 18l-6-6 6-6"></path>
</svg>
</button>
</a>

</div>
<!-- Separator Line -->
<hr
class="mb-1 h-px border-t-0 bg-transparent bg-gradient-to-r from-transparent via-neutral-500 to-transparent opacity-25 dark:via-neutral-400" />


<div id="chatContainer" class="flex-1 overflow-y-auto p-6 space-y-6 h-full no-scrollbar">

<p class="text-center text-gray-500">No messages for this chatbot yet.</p>

</div>

<div id="error" class="text-red-500"></div>

<form id="anonymous-message-form" class="flex w-full space-x-3 px-3 py-1 border-t dark:border-darker border-lighter">

<input type="text" name="query" id="query" required class="block w-full border rounded-3xl p-3 text-lg
bg-white dark:bg-dark focus:outline-none dark:border-darker
focus:ring-2 focus:ring-blue-500 focus:border-blue-500
transition duration-200 ease-in-out shadow-sm" placeholder="Type your message...">
<button type="submit"
class="bg-blue-500 hover:bg-blue-600 text-white rounded-full px-6 py-3 shadow-lg transition duration-200">
<i class="fa-solid fa-paper-plane"></i>
</button>
<button type="button" id="start-record"
class="bg-blue-500 hover:bg-blue-600 text-white rounded-full px-6 py-3 shadow-lg transition duration-200">
<i class="fa-solid fa-microphone"></i>
</button>
<button type="button" id="stop-record"
class="bg-red-500 hover:bg-red-600 text-white rounded-full px-6 shadow-lg transition duration-200 hidden">
<i class="fa-solid fa-stop"></i>
</button>
</form>
</div>
8 changes: 8 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
<div id="main-content">
{% block content %} {% endblock %}
</div>

<a href="{{ url_for('routes.anonymous_chatbot') }}" onclick="navigate(event, '/anonymous');"
class="fixed bottom-16 left-16 bg-blue-600 hover:bg-blue-700 text-white text-3xl w-16 h-16 flex items-center justify-center rounded-full shadow-lg focus:outline-none z-20"
id="anonymousChatbotButton" aria-label="Anonymous Chatbot">
<i class="fas fa-user-secret"></i>
</a>


</div>
<script src="/static/app.js"></script>
</body>
Expand Down

0 comments on commit af3df70

Please sign in to comment.