diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 11594161..8e3324a2 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -42,6 +42,7 @@ from application.utils import ( generate_image_url, safe_filename, validate_function_name, + validate_required_fields, ) from application.vectorstore.vector_creator import VectorCreator @@ -1234,7 +1235,6 @@ class CreateAgent(Resource): if request.content_type == "application/json": data = request.get_json() else: - print(request.form) data = request.form.to_dict() if "tools" in data: try: @@ -1242,11 +1242,18 @@ class CreateAgent(Resource): except json.JSONDecodeError: data["tools"] = [] print(f"Received data: {data}") + if data.get("status") not in ["draft", "published"]: return make_response( - jsonify({"success": False, "message": "Invalid status"}), 400 + jsonify( + { + "success": False, + "message": "Status must be either 'draft' or 'published'", + } + ), + 400, ) - required_fields = [] + if data.get("status") == "published": required_fields = [ "name", @@ -1259,7 +1266,7 @@ class CreateAgent(Resource): ] else: required_fields = ["name"] - missing_fields = check_required_fields(data, required_fields) + missing_fields = validate_required_fields(data, required_fields) if missing_fields: return missing_fields @@ -1270,7 +1277,7 @@ class CreateAgent(Resource): ) try: - key = str(uuid.uuid4()) + key = str(uuid.uuid4()) if data.get("status") == "published" else "" new_agent = { "user": user, "name": data.get("name"), @@ -1455,6 +1462,7 @@ class UpdateAgent(Resource): return make_response( jsonify({"success": False, "message": "No update data provided"}), 400 ) + newly_generated_key = None final_status = update_fields.get("status", existing_agent.get("status")) if final_status == "published": required_published_fields = [ @@ -1484,6 +1492,10 @@ class UpdateAgent(Resource): ), 400, ) + + if not existing_agent.get("key"): + newly_generated_key = str(uuid.uuid4()) + update_fields["key"] = newly_generated_key update_fields["updatedAt"] = datetime.datetime.now(datetime.timezone.utc) try: @@ -1506,7 +1518,7 @@ class UpdateAgent(Resource): jsonify( { "success": True, - "message": "Agent found, but no changes were applied.", + "message": "Agent found, but no changes were applied", } ), 304, @@ -1519,14 +1531,15 @@ class UpdateAgent(Resource): jsonify({"success": False, "message": "Database error during update"}), 500, ) + response_data = { + "success": True, + "id": agent_id, + "message": "Agent updated successfully", + } + if newly_generated_key: + response_data["key"] = newly_generated_key return make_response( - jsonify( - { - "success": True, - "id": agent_id, - "message": "Agent updated successfully", - } - ), + jsonify(response_data), 200, ) diff --git a/application/utils.py b/application/utils.py index 5937179d..883eb926 100644 --- a/application/utils.py +++ b/application/utils.py @@ -80,7 +80,7 @@ def check_required_fields(data, required_fields): jsonify( { "success": False, - "message": f"Missing fields: {', '.join(missing_fields)}", + "message": f"Missing required fields: {', '.join(missing_fields)}", } ), 400, @@ -88,6 +88,29 @@ def check_required_fields(data, required_fields): return None +def validate_required_fields(data, required_fields): + missing_fields = [] + empty_fields = [] + + for field in required_fields: + if field not in data: + missing_fields.append(field) + elif not data[field]: + empty_fields.append(field) + + errors = [] + if missing_fields: + errors.append(f"Missing required fields: {', '.join(missing_fields)}") + if empty_fields: + errors.append(f"Empty values in required fields: {', '.join(empty_fields)}") + + if errors: + return make_response( + jsonify({"success": False, "message": " | ".join(errors)}), 400 + ) + return None + + def get_hash(data): return hashlib.md5(data.encode(), usedforsecurity=False).hexdigest() diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e7c7165d..56a757b4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "copy-to-clipboard": "^3.3.3", "i18next": "^24.2.0", "i18next-browser-languagedetector": "^8.0.2", + "lodash": "^4.17.21", "mermaid": "^11.6.0", "prop-types": "^15.8.1", "react": "^19.1.0", @@ -32,6 +33,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.10", + "@types/lodash": "^4.17.20", "@types/mermaid": "^9.1.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.0.0", @@ -2302,6 +2304,13 @@ "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -7226,6 +7235,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", diff --git a/frontend/package.json b/frontend/package.json index dc434230..cb334bed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "copy-to-clipboard": "^3.3.3", "i18next": "^24.2.0", "i18next-browser-languagedetector": "^8.0.2", + "lodash": "^4.17.21", "mermaid": "^11.6.0", "prop-types": "^15.8.1", "react": "^19.1.0", @@ -43,6 +44,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.10", + "@types/lodash": "^4.17.20", "@types/mermaid": "^9.1.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.0.0", diff --git a/frontend/src/agents/AgentPreview.tsx b/frontend/src/agents/AgentPreview.tsx index 8798a155..ab63c06b 100644 --- a/frontend/src/agents/AgentPreview.tsx +++ b/frontend/src/agents/AgentPreview.tsx @@ -110,7 +110,7 @@ export default function AgentPreview() { }, [queries]); return (
+
This is a preview of the agent. You can publish it to start using it in conversations.
diff --git a/frontend/src/agents/NewAgent.tsx b/frontend/src/agents/NewAgent.tsx index 2162191a..1228a724 100644 --- a/frontend/src/agents/NewAgent.tsx +++ b/frontend/src/agents/NewAgent.tsx @@ -1,3 +1,4 @@ +import isEqual from 'lodash/isEqual'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; @@ -8,6 +9,7 @@ import SourceIcon from '../assets/source.svg'; import Dropdown from '../components/Dropdown'; import { FileUpload } from '../components/FileUpload'; import MultiSelectPopup, { OptionType } from '../components/MultiSelectPopup'; +import Spinner from '../components/Spinner'; import AgentDetailsModal from '../modals/AgentDetailsModal'; import ConfirmationModal from '../modals/ConfirmationModal'; import { ActiveState, Doc, Prompt } from '../models/misc'; @@ -66,34 +68,41 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) { useState