From f39ec1b7bf1de70b7de95f97c6d9a864d7b96e40 Mon Sep 17 00:00:00 2001 From: Priyanshu Verma Date: Fri, 1 Nov 2024 11:20:09 +0000 Subject: [PATCH] feat: Chatbot Version Control --- app/api_routes.py | 107 ++++++++++++++--- app/helpers.py | 49 ++++---- app/models.py | 67 ++++++++--- client/bun.lockb | Bin 316546 -> 318086 bytes client/src/components/ChatbotCard.tsx | 19 ++- .../modals/update-chatbot-modal.tsx | 109 +++++++++++++++++- client/src/lib/queries.ts | 1 + client/src/pages/Chatbot.tsx | 15 ++- client/src/pages/ChatbotView.tsx | 24 ++-- client/src/pages/Dashboard.tsx | 8 +- client/src/pages/Hub.tsx | 2 +- client/src/pages/Profile.tsx | 2 +- client/src/types/types.d.ts | 14 ++- 13 files changed, 330 insertions(+), 87 deletions(-) diff --git a/app/api_routes.py b/app/api_routes.py index 106a16a..63e1e72 100644 --- a/app/api_routes.py +++ b/app/api_routes.py @@ -2,8 +2,9 @@ import re import os from sqlalchemy import func -from .models import User, Chatbot, Chat, Image, Comment +from .models import User, Chatbot, Chat, Image, Comment, ChatbotVersion from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import joinedload from flask_login import login_user from typing import Union, List, Optional, Dict from .ai import chat_with_chatbot, text_to_mp3, translate_text @@ -159,14 +160,21 @@ def api_create_chatbot() -> Response: user = get_current_user() chatbot: Chatbot = Chatbot( - name=chatbot_name, + avatar=f"{BOT_AVATAR_API}/{chatbot_name}", user_id=user.id, - prompt=chatbot_prompt, - generated_by=user.username, + public=False, # Set public to default (modify as needed) category=chatbot_category, - avatar=f"{BOT_AVATAR_API}/{chatbot_name}", + likes=0, # Default likes + reports=0, # Default reports ) db.session.add(chatbot) + db.session.flush() # Flush to get chatbot ID before creating version + + # Create the initial version of the chatbot + chatbot.create_version( + name=chatbot_name, new_prompt=chatbot_prompt, modified_by=user.username + ) + user.contribution_score += 5 db.session.commit() return jsonify({"success": True, "message": "Chatbot created."}) @@ -184,13 +192,65 @@ def api_update_chatbot(chatbot_id: int) -> Union[Response, str]: ) data = request.get_json() - chatbot.name = data.get("name") - chatbot.prompt = data.get("prompt") - chatbot.category = data.get("category") + new_name = data.get("name") + new_prompt = data.get("prompt") + new_category = data.get("category") + # Create a new version of the chatbot with the updated prompt and other details + chatbot.create_version( + name=new_name, new_prompt=new_prompt, modified_by=user.username + ) + + # Update the chatbot's other fields + chatbot.avatar = f"{BOT_AVATAR_API}/{new_name}" # Update avatar if needed + chatbot.category = new_category db.session.commit() return jsonify({"success": True, "message": "Chatbot Updated."}) +@api_bp.route("/api/chatbot//revert/", methods=["POST"]) +@jwt_required() +def api_revert_chatbot(chatbot_id: int, version_id: int) -> Union[Response, str]: + """API endpoint to revert a chatbot to a previous version.""" + current_user = get_jwt_identity() + + # Fetch the chatbot by ID and ensure it belongs to the current user + chatbot = Chatbot.query.filter_by(id=chatbot_id, user_id=current_user).first() + if chatbot is None: + return jsonify({"success": False, "message": "Chatbot not found."}), 404 + + # Fetch the specified version by ID + version = ChatbotVersion.query.filter_by( + id=version_id, chatbot_id=chatbot_id + ).first() + if version is None: + return jsonify({"success": False, "message": "Version not found."}), 404 + + chatbot.latest_version_id = version.id # Update to reflect the latest version + + try: + # Commit changes to the database + db.session.commit() + return jsonify( + { + "success": True, + "message": "Chatbot reverted to the selected version.", + "version": version.to_dict(), + } + ) # Ensure to return the updated version info + except Exception as e: + db.session.rollback() + return ( + jsonify( + { + "success": False, + "message": "Failed to revert the chatbot.", + "error": str(e), + } + ), + 500, + ) + + @api_bp.route("/api/chatbot//delete", methods=["POST"]) @jwt_required() def api_delete_chatbot(chatbot_id: int) -> Union[Response, tuple[Response, int]]: @@ -202,7 +262,7 @@ def api_delete_chatbot(chatbot_id: int) -> Union[Response, tuple[Response, int]] jsonify({"error": "Unauthorized access."}), 403, ) - + ChatbotVersion.query.filter_by(chatbot_id=chatbot.id).delete() db.session.delete(chatbot) db.session.commit() @@ -502,28 +562,31 @@ def api_get_data(): "leaderboard", } queues = [q for q in queues if q in valid_queues] - - # Fetch data - chatbots: List[Chatbot] = Chatbot.query.filter(Chatbot.user_id == uid).all() - images: List[Image] = Image.query.filter(Image.user_id == uid).all() - system_chatbots: List[Chatbot] = Chatbot.query.filter( - Chatbot.generated_by == "system" - ).all() - public_chatbots: List[Chatbot] = Chatbot.query.filter_by(public=True).all() - public_images: List[Image] = Image.query.filter_by(public=True).all() - response = {"success": True} # Build response based on queues if "system_bots" in queues: + system_chatbots: List[Chatbot] = ( + Chatbot.query.options( + joinedload(Chatbot.latest_version) + ) # Use joinedload for eager loading + .filter( + Chatbot.latest_version.has(modified_by="system") + ) # Use has() for filtering on related model + .all() + ) response["system_bots"] = [bot.to_dict() for bot in system_chatbots] if "my_bots" in queues: + chatbots: List[Chatbot] = Chatbot.query.filter(Chatbot.user_id == uid).all() response["my_bots"] = [bot.to_dict() for bot in chatbots] if "my_images" in queues: + images: List[Image] = Image.query.filter(Image.user_id == uid).all() response["my_images"] = [image.to_dict() for image in images] if "public_bots" in queues: + public_chatbots: List[Chatbot] = Chatbot.query.filter_by(public=True).all() response["public_bots"] = [bot.to_dict() for bot in public_chatbots] if "public_images" in queues: + public_images: List[Image] = Image.query.filter_by(public=True).all() response["public_images"] = [image.to_dict() for image in public_images] if "user_bots" in queues: o_chatbots: List[Chatbot] = Chatbot.query.filter( @@ -644,12 +707,18 @@ def api_get_chatbot_data(chatbot_id: str): chatbot: Chatbot = Chatbot.query.get(chatbot_id) if chatbot == None: return jsonify({"success": False, "message": "Chatbot not found"}), 404 + versions = ( + ChatbotVersion.query.filter_by(chatbot_id=chatbot_id) + .order_by(ChatbotVersion.version_number.desc()) + .all() + ) comments: List[Comment] = Comment.query.filter_by(chatbot_id=chatbot_id).all() return ( jsonify( { "success": True, "bot": chatbot.to_dict(), + "versions": [version.to_dict() for version in versions], "comments": [comment.to_dict() for comment in comments], } ), diff --git a/app/helpers.py b/app/helpers.py index 09c501f..49f300b 100644 --- a/app/helpers.py +++ b/app/helpers.py @@ -3,38 +3,45 @@ from .constants import BOT_AVATAR_API, DEFAULT_CHATBOTS import logging +# Setup logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) handler = logging.FileHandler("chatbot_creation.log") -handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) +handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) logger.addHandler(handler) + def create_default_chatbots(db): """Create default chatbots if none exist.""" - - if Chatbot.query.count() == 0: - try: + try: + if Chatbot.query.count() == 0: for bot_data in DEFAULT_CHATBOTS: - required_fields = ["name", "prompt", "generated_by"] - for field in required_fields: - if field not in bot_data: - logger.error(f"Missing required field '{field}' in bot_data: {bot_data}") - continue - avatar = f"{BOT_AVATAR_API}/{bot_data['name']}" chatbot = Chatbot( - name=bot_data["name"], - prompt=bot_data["prompt"], - generated_by=bot_data["generated_by"], - user_id=bot_data["user_id"], + public=True, # Set to True as per your requirements + category="General", # Default category if not specified + likes=0, # Initialize likes + reports=0, # Initialize reports avatar=avatar, - public=True, + user_id=None, ) + db.session.add(chatbot) + db.session.flush() # Ensure chatbot ID is available for version creation + + # Create an initial version for the chatbot + chatbot.create_version( + name=bot_data["name"], + new_prompt=bot_data["prompt"], + modified_by=bot_data["generated_by"], + ) + db.session.commit() - logger.info("Default chatbots created successfully.") - except Exception as e: - db.session.rollback() - error_message = f"Error creating default chatbots: {str(e)}" - flash(error_message, "error") - logger.error(error_message) + logger.info( + "Default chatbots and their initial versions created successfully." + ) + except Exception as e: + db.session.rollback() + error_message = f"Error creating default chatbots: {str(e)}" + flash(error_message, "error") + logger.error(error_message) diff --git a/app/models.py b/app/models.py index a9800e6..30e3c79 100644 --- a/app/models.py +++ b/app/models.py @@ -44,31 +44,72 @@ class Chatbot(db.Model): __tablename__ = "chatbots" id: int = db.Column(db.Integer, primary_key=True) - name: str = db.Column(db.String(100), nullable=False) avatar: str = db.Column(db.Text, nullable=False) - prompt: str = db.Column(db.Text, nullable=False) - generated_by: str = db.Column(db.String(10), nullable=False) user_id: int = db.Column(db.Integer, nullable=True) public: bool = db.Column(db.Boolean, default=False) - category: str = db.Column(db.Text, default="General", nullable=False) + category = db.Column(db.Text, default="General", nullable=False) likes: int = db.Column(db.Integer, default=0, nullable=False) reports: int = db.Column(db.Integer, default=0, nullable=False) + # Linking to the latest version + latest_version_id = db.Column( + db.Integer, db.ForeignKey("chatbot_versions.id"), nullable=True + ) + latest_version = db.relationship( + "ChatbotVersion", backref="chatbot", foreign_keys=[latest_version_id] + ) - def __repr__(self) -> str: - return f"" - - def to_dict(self) -> dict: + def create_version(self, name, new_prompt, modified_by): + version = ChatbotVersion( + chatbot_id=self.id, + version_number=( + (self.latest_version.version_number + 1) if self.latest_version else 1 + ), + name=name, + prompt=new_prompt, + modified_by=modified_by, + ) + db.session.add(version) + db.session.flush() # Get the ID of the new version + self.latest_version_id = version.id + db.session.commit() + + def to_dict(self): return { "id": self.id, - "name": self.name, - "avatar": self.avatar, - "prompt": self.prompt, "public": self.public, - "user_id": self.user_id, "category": self.category, - "generated_by": self.generated_by, + "user_id": self.user_id, "likes": self.likes, + "avatar": self.avatar, # Include avatar in the dictionary "reports": self.reports, + "latest_version": ( + self.latest_version.to_dict() if self.latest_version else None + ), + } + + +class ChatbotVersion(db.Model): + __tablename__ = "chatbot_versions" + + id = db.Column(db.Integer, primary_key=True) + chatbot_id = db.Column(db.Integer, db.ForeignKey("chatbots.id"), nullable=False) + version_number = db.Column(db.Integer, nullable=False) + prompt = db.Column(db.Text, nullable=False) + name = db.Column(db.String(100), nullable=False) # Added field for name + modified_by = db.Column(db.String(100), nullable=False) + created_at = db.Column( + db.DateTime(timezone=True), server_default=func.now(), nullable=False + ) + + def to_dict(self): + return { + "id": self.id, + "chatbot_id": self.chatbot_id, + "version_number": self.version_number, + "prompt": self.prompt, + "name": self.name, # Include name in the dictionary + "modified_by": self.modified_by, + "created_at": self.created_at.isoformat(), } diff --git a/client/bun.lockb b/client/bun.lockb index 2c4007ba07443616c5963b414d5d0d2648d2ff54..8cc0766badfe0c3f82da3146adef016e75567014 100755 GIT binary patch delta 60338 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 YJLr~Be@ER0WbUZDAfwz-H+l2_0Dw+tGXMYp delta 59713 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¥sPfu)P!Rur=1J;w7 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 - + - {chatbot.name.slice(0, 2).toUpperCase()} + {chatbot.latest_version.name.slice(0, 2).toUpperCase()}
-

{chatbot.name}

+

+ {chatbot.latest_version.name} +

- Created by @{chatbot.generated_by} + Created by @{chatbot.latest_version.modified_by}

-

"{chatbot.prompt.substring(0, 100)}"

+

+ "{chatbot.latest_version.prompt.substring(0, 100)}" +

@@ -100,7 +107,7 @@ export function ChatbotCard({ size="icon" onClick={() => shareModel.onOpen({ - title: `Share Chatbot ${chatbot.name} Powered by Bot Verse`, + title: `Share Chatbot ${chatbot.latest_version.name} Powered by Bot Verse`, shareUrl: `http://localhost:5000/hub/${chatbot.id}`, }) } diff --git a/client/src/components/modals/update-chatbot-modal.tsx b/client/src/components/modals/update-chatbot-modal.tsx index 5091056..2898c0a 100644 --- a/client/src/components/modals/update-chatbot-modal.tsx +++ b/client/src/components/modals/update-chatbot-modal.tsx @@ -26,7 +26,7 @@ import { z } from "zod"; import { createChatbotSchema } from "@/lib/schemas"; import { zodResolver } from "@hookform/resolvers/zod"; import { chatbotCategories, SERVER_URL } from "@/lib/utils"; -import { Loader2 } from "lucide-react"; +import { Loader2, StepBack } from "lucide-react"; import { Textarea } from "../ui/textarea"; import { useQueryClient } from "@tanstack/react-query"; import { @@ -37,11 +37,26 @@ import { SelectValue, } from "../ui/select"; +interface Extras { + id: number; + prevPrompt: string; + prevName: string; + prevCategory: string; + versions: ChatbotVersion[]; +} + export default function UpdateChatbotModal() { const modal = useUpdateChatbotModal(); - const { id, prevName, prevPrompt, prevCategory } = modal.extras; - + const { + id, + prevName, + prevPrompt, + prevCategory, + versions: initialVersions, + }: Extras = modal.extras; + const [selectedVersion, setSelectedVersion] = useState(null); // State for selected version + const [versions, setVersions] = useState([]); const [loading, setLoading] = useState(false); // Loading state for request const rq = useQueryClient(); const form = useForm>({ @@ -60,8 +75,9 @@ export default function UpdateChatbotModal() { prompt: prevPrompt, category: prevCategory, }); + setVersions(initialVersions); } - }, [modal.isOpen, prevName, prevPrompt, prevCategory]); // Depend on modal open state and initial text + }, [modal.isOpen, prevName, prevPrompt, prevCategory, initialVersions]); // Depend on modal open state and initial text async function onSubmit(values: z.infer) { try { @@ -78,9 +94,10 @@ export default function UpdateChatbotModal() { ); if (response.data?.success) { toast.success("Updated!"); + await rq.invalidateQueries({ queryKey: ["chatbot_view", id] }); + modal.onClose(); form.reset(); - rq.invalidateQueries({ queryKey: ["dashboard"] }); } else { throw new Error(response.data?.message || "failed. Please try again."); } @@ -95,6 +112,42 @@ export default function UpdateChatbotModal() { setLoading(false); } } + async function onRevert() { + if (selectedVersion) { + try { + const token = localStorage.getItem("token"); + const authHeaders = { + Authorization: `Bearer ${token || ""}`, + }; + setLoading(true); + const response = await axios.post( + `${SERVER_URL}/api/chatbot/${id}/revert/${selectedVersion}`, + {}, + { headers: authHeaders } + ); + + if (response.data?.success) { + toast.success("Reverted to selected version!"); + await rq.invalidateQueries({ queryKey: ["chatbot_view", id] }); + modal.onClose(); + form.reset(); + } else { + throw new Error( + response.data?.message || "Failed to revert. Please try again." + ); + } + } catch (error: any) { + const errorMessage = + error.response?.data?.message || + error.message || + "An unexpected error occurred."; + toast.error(errorMessage); + console.log("[REVERT_ERROR]", error); + } finally { + setLoading(false); + } + } + } if (form.getValues().name === "") { form.reset({ @@ -172,6 +225,52 @@ export default function UpdateChatbotModal() { )} /> + {versions.length <= 1 ? null : ( + <> +
+ Select Version to Revert + +
+ + + )} + diff --git a/client/src/lib/queries.ts b/client/src/lib/queries.ts index e035d16..3414160 100644 --- a/client/src/lib/queries.ts +++ b/client/src/lib/queries.ts @@ -44,6 +44,7 @@ export const fetchChatbotViewData = async ( ): Promise<{ bot: Chatbot; comments: ChatbotComment[]; + versions: ChatbotVersion[]; }> => { const token = localStorage.getItem("token"); diff --git a/client/src/pages/Chatbot.tsx b/client/src/pages/Chatbot.tsx index ff85ae0..64dea5f 100644 --- a/client/src/pages/Chatbot.tsx +++ b/client/src/pages/Chatbot.tsx @@ -44,7 +44,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import useSpeech from "@/hooks/useSpeech"; -import EmojiPicker from 'emoji-picker-react'; +import EmojiPicker from "emoji-picker-react"; export default function ChatbotPage() { const { id } = useParams(); @@ -159,11 +159,11 @@ export default function ChatbotPage() { {`${data?.bot.name}'s

- {data?.bot.name} + {data?.bot.latest_version.name}

)} @@ -288,11 +288,14 @@ export default function ChatbotPage() { type="button" onClick={() => setShowEmojiPicker(!showEmojiPicker)} className="p-2 bg-gray-200 rounded-full" - >😊 + > + 😊 {showEmojiPicker && ( -
{/* Positioned above */} - +
+ {" "} + {/* Positioned above */} +
)}