From 0ecae19363da81c5afb6b51468da57c3bdf299f5 Mon Sep 17 00:00:00 2001 From: Priyanshu Verma Date: Fri, 11 Oct 2024 15:39:20 +0000 Subject: [PATCH] feat: anon bot --- api_routes.py | 51 +++++++++++++++- routes.py | 10 +++ static/app.js | 17 ++++++ static/js/anonymous.js | 128 +++++++++++++++++++++++++++++++++++++++ templates/anonymous.html | 56 +++++++++++++++++ templates/base.html | 8 +++ 6 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 static/js/anonymous.js create mode 100644 templates/anonymous.html diff --git a/api_routes.py b/api_routes.py index 3c5617e..7b306aa 100644 --- a/api_routes.py +++ b/api_routes.py @@ -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 @@ -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, + } + ) diff --git a/routes.py b/routes.py index 4ee21c1..4f365b9 100644 --- a/routes.py +++ b/routes.py @@ -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: diff --git a/static/app.js b/static/app.js index 86d78ec..bbf9634 100644 --- a/static/app.js +++ b/static/app.js @@ -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) { @@ -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 @@ -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(); diff --git a/static/js/anonymous.js b/static/js/anonymous.js new file mode 100644 index 0000000..69187fa --- /dev/null +++ b/static/js/anonymous.js @@ -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 = `

No messages for this chatbot yet.

`; + return; + } + + updatedChats.forEach((chat) => { + // Append user message + if (chat.role === "user") { + // Use === for comparison + const userMessage = ` +
+
+

${chat.content}

+
+
`; + chatContainer.insertAdjacentHTML("beforeend", userMessage); + } + + // Append assistant response + if (chat.role === "assistant") { + // Check if the role is assistant + const assistantResponse = ` +
+
+

+ ${chat.content} +

+
+
`; + 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); diff --git a/templates/anonymous.html b/templates/anonymous.html new file mode 100644 index 0000000..fd329b9 --- /dev/null +++ b/templates/anonymous.html @@ -0,0 +1,56 @@ +{% if full_page %} +{% extends 'base.html' %} +{% endif %} + +
+
+
+ Anonymous's avatar +

Anonymous Chatbot

+
+ + + + +
+ +
+ + +
+ +

No messages for this chatbot yet.

+ +
+ +
+ +
+ + + + + +
+
\ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 340bbdc..4cd52ea 100644 --- a/templates/base.html +++ b/templates/base.html @@ -23,6 +23,14 @@
{% block content %} {% endblock %}
+ + + + + +