diff --git a/.dockerignore b/.dockerignore index b090bf637..dfb96d0a9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,18 @@ -venv/ -gui/ \ No newline at end of file +# Ignore everything +** + +# Allow files and directories +!/migrations +!/nginx +!/superagi +!/tgwui +!/tools +!/workspace +!/main.py +!/requirements.txt +!/entrypoint.sh +!/entrypoint_celery.sh +!/wait-for-it.sh +!/tools.json +!/install_tool_dependencies.sh +!/alembic.ini \ No newline at end of file diff --git a/.gitignore b/.gitignore index ab92910c2..b846e8c74 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ workspace/output workspace/input celerybeat-schedule ../bfg-report* +superagi/tools/marketplace_tools/ +superagi/tools/external_tools/ +tests/unit_tests/resource_manager/test_path +tools.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 775a0d5dc..86f98bf95 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,33 @@ -FROM python:3.9 +# Stage 1: Compile image +FROM python:3.10-slim-bullseye AS compile-image WORKDIR /app -COPY requirements.txt . -#RUN apt-get update && apt-get install --no-install-recommends -y git wget libpq-dev gcc python3-dev && pip install psycopg2 -RUN pip install --upgrade pip -RUN pip install --no-cache-dir -r requirements.txt +RUN apt-get update && \ + apt-get install --no-install-recommends -y wget libpq-dev gcc g++ python3-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +COPY requirements.txt . +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt COPY . . -COPY config.yaml ./config.yaml -COPY entrypoint.sh ./entrypoint.sh -COPY wait-for-it.sh ./wait-for-it.sh -RUN chmod +x ./entrypoint.sh ./wait-for-it.sh -CMD ["./wait-for-it.sh", "super__postgres:5432","-t","60","--","./entrypoint.sh"] +RUN chmod +x ./entrypoint.sh ./wait-for-it.sh ./install_tool_dependencies.sh ./entrypoint_celery.sh + +# Stage 2: Build image +FROM python:3.10-slim-bullseye AS build-image +WORKDIR /app + +RUN apt-get update && \ + apt-get install --no-install-recommends -y libpq-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=compile-image /opt/venv /opt/venv +COPY --from=compile-image /app /app + +ENV PATH="/opt/venv/bin:$PATH" diff --git a/DockerfileCelery b/DockerfileCelery index 0f8625679..682e50824 100644 --- a/DockerfileCelery +++ b/DockerfileCelery @@ -8,9 +8,16 @@ RUN pip install --upgrade pip COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -WORKDIR /app COPY . . -COPY config.yaml . + +# Downloads the tools +RUN python superagi/tool_manager.py + +# Set executable permissions for install_tool_dependencies.sh +RUN chmod +x install_tool_dependencies.sh + +# Install dependencies +RUN ./install_tool_dependencies.sh # Downloads the tools RUN python superagi/tool_manager.py diff --git a/config_template.yaml b/config_template.yaml index 916b1b340..fedd3b642 100644 --- a/config_template.yaml +++ b/config_template.yaml @@ -38,6 +38,7 @@ RESOURCES_OUTPUT_ROOT_DIR: workspace/output/{agent_id}/{agent_execution_id} # Fo #S3 RELATED DETAILS ONLY WHEN STORAGE_TYPE IS "S3" BUCKET_NAME: +INSTAGRAM_TOOL_BUCKET_NAME: #Public read bucket, Images generated by stable diffusion are put in this bucket and the public url of the same is generated. AWS_ACCESS_KEY_ID: AWS_SECRET_ACCESS_KEY: diff --git a/docker-compose.image.example.yaml b/docker-compose.image.example.yaml new file mode 100644 index 000000000..c5c393d7e --- /dev/null +++ b/docker-compose.image.example.yaml @@ -0,0 +1,75 @@ +version: '3.8' +services: + backend: + image: "superagidev/backend:dev" + depends_on: + - super__redis + - super__postgres + networks: + - super_network + env_file: + - config.yaml + command: ["/app/wait-for-it.sh", "super__postgres:5432","-t","60","--","/app/entrypoint.sh"] + + celery: + image: "superagidev/backend:dev" + depends_on: + - super__redis + - super__postgres + networks: + - super_network + env_file: + - config.yaml + command: ["/app/entrypoint_celery.sh"] + volumes: + - "./workspace:/app/workspace" + + gui: + image: "superagidev/gui:dev" + environment: + - NEXT_PUBLIC_API_BASE_URL=/api + networks: + - super_network + + super__redis: + image: "redis/redis-stack-server:latest" + networks: + - super_network +# uncomment to expose redis port to host +# ports: +# - "6379:6379" + volumes: + - redis_data:/data + + super__postgres: + image: "docker.io/library/postgres:latest" + environment: + - POSTGRES_USER=superagi + - POSTGRES_PASSWORD=password + - POSTGRES_DB=super_agi_main + volumes: + - superagi_postgres_data:/var/lib/postgresql/data/ + networks: + - super_network +# uncomment to expose postgres port to host +# ports: +# - "5432:5432" + + proxy: + image: nginx:stable-alpine + ports: + - "3000:80" + networks: + - super_network + depends_on: + - backend + - gui + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + +networks: + super_network: + driver: bridge +volumes: + superagi_postgres_data: + redis_data: diff --git a/docker-compose.yaml b/docker-compose.yaml index 6dabb981d..35a089433 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,22 +9,23 @@ services: - super__postgres networks: - super_network + command: ["/app/wait-for-it.sh", "super__postgres:5432","-t","60","--","/app/entrypoint.sh"] celery: volumes: - "./:/app" - "${EXTERNAL_RESOURCE_DIR:-./workspace}:/app/ext" - build: - context: . - dockerfile: DockerfileCelery + build: . depends_on: - super__redis - super__postgres networks: - super_network + command: ["/app/entrypoint_celery.sh"] gui: - build: ./gui - environment: - - NEXT_PUBLIC_API_BASE_URL=/api + build: + context: ./gui + args: + NEXT_PUBLIC_API_BASE_URL: "/api" networks: - super_network # volumes: diff --git a/entrypoint.sh b/entrypoint.sh index da37ae04b..56b75aab3 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,11 +1,8 @@ #!/bin/bash -# Downloads the tools +# Downloads the tools from marketplace and external tool repositories python superagi/tool_manager.py -# Set executable permissions for install_tool_dependencies.sh -chmod +x install_tool_dependencies.sh - # Install dependencies ./install_tool_dependencies.sh diff --git a/entrypoint_celery.sh b/entrypoint_celery.sh old mode 100644 new mode 100755 index 0b2b8a531..a5d7b5491 --- a/entrypoint_celery.sh +++ b/entrypoint_celery.sh @@ -1,5 +1,9 @@ #!/bin/bash -Xvfb :0 -screen 0 1280x1024x24 & -x11vnc -display :0 -N -forever -shared & -exec "$@" \ No newline at end of file +# Downloads the tools +python superagi/tool_manager.py + +# Install dependencies +./install_tool_dependencies.sh + +exec celery -A superagi.worker worker --beat --loglevel=info \ No newline at end of file diff --git a/gui/.dockerignore b/gui/.dockerignore index 220520759..42cdf5d4e 100644 --- a/gui/.dockerignore +++ b/gui/.dockerignore @@ -1,2 +1,13 @@ -node_modules/ -.next/ \ No newline at end of file +# Ignore everything +** + +# Allow files and directories +!app +!pages +!public +!utils +!package.json +!next.config.js +!package-lock.json +!.eslintrc.json +!jsconfig.json \ No newline at end of file diff --git a/gui/Dockerfile b/gui/Dockerfile index e6a63f58d..bdfe1cedb 100644 --- a/gui/Dockerfile +++ b/gui/Dockerfile @@ -1,13 +1,19 @@ -FROM node:lts AS deps - +FROM node:18-alpine AS deps +RUN apk add --no-cache libc6-compat WORKDIR /app - -COPY package*.json ./ +COPY package.json package-lock.json ./ RUN npm ci -FROM node:lts AS builder +# Rebuild the source code only when needed +FROM node:18-alpine AS builder + WORKDIR /app -COPY . . + COPY --from=deps /app/node_modules ./node_modules +COPY . . +ARG NEXT_PUBLIC_API_BASE_URL=/api +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +EXPOSE 3000 + CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/gui/DockerfileProd b/gui/DockerfileProd index e76962175..aa3186411 100644 --- a/gui/DockerfileProd +++ b/gui/DockerfileProd @@ -1,11 +1,43 @@ -FROM node:lts +FROM node:18-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --only=production + +# Rebuild the source code only when needed +FROM node:18-alpine AS builder WORKDIR /app -COPY package.json . -RUN npm install +COPY --from=deps /app/node_modules ./node_modules COPY . . +ARG NEXT_PUBLIC_API_BASE_URL=/api +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL RUN npm run build -CMD ["npm", "run", "start"] \ No newline at end of file + +# Production image, copy all the files and run next +FROM node:18-alpine AS runner +WORKDIR /app + +ENV NODE_ENV production + +RUN addgroup --system --gid 1001 supergroup +RUN adduser --system --uid 1001 superuser + +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=superuser:supergroup /app/.next/standalone ./ +COPY --from=builder --chown=superuser:supergroup /app/.next/static ./.next/static + +USER superuser + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/gui/next.config.js b/gui/next.config.js index 74c156b97..bd31d6ce9 100644 --- a/gui/next.config.js +++ b/gui/next.config.js @@ -1,6 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { assetPrefix: process.env.NODE_ENV === "production" ? "/" : "./", + output: 'standalone' }; module.exports = nextConfig; diff --git a/gui/package-lock.json b/gui/package-lock.json index b0a186e98..ebb65645d 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -27,6 +27,7 @@ "react-draggable": "^4.4.5", "react-grid-layout": "^1.3.4", "react-markdown": "^8.0.7", + "react-spinners": "^0.13.8", "react-toastify": "^9.1.3" } }, @@ -3683,6 +3684,15 @@ "react": ">= 16.3" } }, + "node_modules/react-spinners": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz", + "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-toastify": { "version": "9.1.3", "license": "MIT", diff --git a/gui/package.json b/gui/package.json index b9b588838..f788aee8d 100644 --- a/gui/package.json +++ b/gui/package.json @@ -29,6 +29,7 @@ "react-draggable": "^4.4.5", "react-grid-layout": "^1.3.4", "react-markdown": "^8.0.7", + "react-spinners": "^0.13.8", "react-toastify": "^9.1.3" } } diff --git a/gui/pages/Content/APM/ApmDashboard.js b/gui/pages/Content/APM/ApmDashboard.js index 510c2ee43..939d7f3b0 100644 --- a/gui/pages/Content/APM/ApmDashboard.js +++ b/gui/pages/Content/APM/ApmDashboard.js @@ -3,7 +3,7 @@ import Image from "next/image"; import style from "./Apm.module.css"; import 'react-toastify/dist/ReactToastify.css'; import {getActiveRuns, getAgentRuns, getAllAgents, getToolsUsage, getMetrics} from "@/pages/api/DashboardService"; -import {formatNumber, formatTime} from "@/utils/utils"; +import {formatNumber, formatTime, returnToolkitIcon} from "@/utils/utils"; import {BarGraph} from "./BarGraph.js"; import {WidthProvider, Responsive} from 'react-grid-layout'; import 'react-grid-layout/css/styles.css'; @@ -11,7 +11,7 @@ import 'react-resizable/css/styles.css'; const ResponsiveGridLayout = WidthProvider(Responsive); -export default function ApmDashboard() { +export default function ApmDashboard(key) { const [agentDetails, setAgentDetails] = useState([]); const [tokenDetails, setTokenDetails] = useState([]); const [runDetails, setRunDetails] = useState(0); @@ -221,7 +221,11 @@ export default function ApmDashboard() { {toolsUsed.map((tool, index) => ( - {tool.tool_name} + + tool-icon + {tool.tool_name} + {tool.unique_agents} {tool.total_usage} diff --git a/gui/pages/Content/Agents/ActivityFeed.js b/gui/pages/Content/Agents/ActivityFeed.js index 467575aea..25258c7db 100644 --- a/gui/pages/Content/Agents/ActivityFeed.js +++ b/gui/pages/Content/Agents/ActivityFeed.js @@ -4,6 +4,7 @@ import {getExecutionFeeds, getDateTime} from "@/pages/api/DashboardService"; import Image from "next/image"; import {loadingTextEffect, formatTimeDifference} from "@/utils/utils"; import {EventBus} from "@/utils/eventBus"; +import {ClipLoader} from 'react-spinners'; export default function ActivityFeed({selectedRunId, selectedView, setFetchedData, agent}) { const [loadingText, setLoadingText] = useState("Thinking"); @@ -13,6 +14,7 @@ export default function ActivityFeed({selectedRunId, selectedView, setFetchedDat const [prevFeedsLength, setPrevFeedsLength] = useState(0); const [scheduleDate, setScheduleDate] = useState(null); const [scheduleTime, setScheduleTime] = useState(null); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { const interval = window.setInterval(function () { @@ -73,6 +75,7 @@ export default function ActivityFeed({selectedRunId, selectedView, setFetchedDat function fetchFeeds() { if (selectedRunId !== null) { + setIsLoading(true); getExecutionFeeds(selectedRunId) .then((response) => { const data = response.data; @@ -80,9 +83,11 @@ export default function ActivityFeed({selectedRunId, selectedView, setFetchedDat setRunStatus(data.status); setFetchedData(data.permissions); EventBus.emit('resetRunStatus', {executionId: selectedRunId, status: data.status}); + setIsLoading(false); //add this line }) .catch((error) => { console.error('Error fetching execution feeds:', error); + setIsLoading(false); // and this line }); } } @@ -160,8 +165,20 @@ export default function ActivityFeed({selectedRunId, selectedView, setFetchedDat } } - {!agent?.is_scheduled && !agent?.is_running && feeds.length < 1 && -
The Agent is not scheduled
+ {feeds.length < 1 && !agent?.is_running && !agent?.is_scheduled ? + (isLoading ? +
+ +
+ :
The Agent is not scheduled
) : null } {feedContainerRef.current && feedContainerRef.current.scrollTop >= 1200 && diff --git a/gui/pages/Content/Agents/AgentCreate.js b/gui/pages/Content/Agents/AgentCreate.js index b4a7bb023..a55d69144 100644 --- a/gui/pages/Content/Agents/AgentCreate.js +++ b/gui/pages/Content/Agents/AgentCreate.js @@ -2,9 +2,9 @@ import React, {useState, useEffect, useRef} from 'react'; import Image from "next/image"; import {ToastContainer, toast} from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import styles from './Agents.module.css'; import { createAgent, + editAgentTemplate, fetchAgentTemplateConfigLocal, getOrganisationConfig, getLlmModels, @@ -16,15 +16,29 @@ import { openNewTab, removeTab, setLocalStorageValue, - setLocalStorageArray, returnResourceIcon, getUserTimezone, createInternalId, excludedToolkits + setLocalStorageArray, returnResourceIcon, getUserTimezone, createInternalId, preventDefault, excludedToolkits } from "@/utils/utils"; import {EventBus} from "@/utils/eventBus"; +import styles from "@/pages/Content/Agents/Agents.module.css"; +import styles1 from "@/pages/Content/Knowledge/Knowledge.module.css"; import 'moment-timezone'; import AgentSchedule from "@/pages/Content/Agents/AgentSchedule"; -export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgents, toolkits, organisationId, template, internalId, env}) { +export default function AgentCreate({ + sendAgentData, + knowledge, + selectedProjectId, + fetchAgents, + toolkits, + organisationId, + template, + internalId, + sendKnowledgeData, + env + }) { const [advancedOptions, setAdvancedOptions] = useState(false); const [agentName, setAgentName] = useState(""); + const [agentTemplateId, setAgentTemplateId] = useState(null); const [agentDescription, setAgentDescription] = useState(""); const [longTermMemory, setLongTermMemory] = useState(true); const [addResources, setAddResources] = useState(true); @@ -35,6 +49,8 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen const [maxIterations, setIterations] = useState(25); const [toolkitList, setToolkitList] = useState(toolkits) const [searchValue, setSearchValue] = useState(''); + const [showButton, setShowButton] = useState(false); + const [showPlaceholder, setShowPlaceholder] = useState(true); const constraintsArray = [ "If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.", @@ -67,6 +83,11 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen const rollingRef = useRef(null); const [rollingDropdown, setRollingDropdown] = useState(false); + const [selectedKnowledge, setSelectedKnowledge] = useState(''); + const [selectedKnowledgeId, setSelectedKnowledgeId] = useState(null); + const knowledgeRef = useRef(null); + const [knowledgeDropdown, setKnowledgeDropdown] = useState(false); + const databases = ["Pinecone"] const [database, setDatabase] = useState(databases[0]); const databaseRef = useRef(null); @@ -125,7 +146,7 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen const models = response.data || []; const selected_model = localStorage.getItem("agent_model_" + String(internalId)) || ''; setModelsArray(models); - if(models.length > 0 && !selected_model) { + if (models.length > 0 && !selected_model) { setLocalStorageValue("agent_model_" + String(internalId), models[0], setModel); } else { setModel(selected_model); @@ -139,6 +160,7 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen setLocalStorageValue("agent_name_" + String(internalId), template.name, setAgentName); setLocalStorageValue("agent_description_" + String(internalId), template.description, setAgentDescription); setLocalStorageValue("advanced_options_" + String(internalId), true, setAdvancedOptions); + setLocalStorageValue("agent_template_id_" + String(internalId), template.id, setAgentTemplateId); fetchAgentTemplateConfigLocal(template.id) .then((response) => { @@ -153,6 +175,8 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen setLocalStorageValue("agent_database_" + String(internalId), data.LTM_DB, setDatabase); setLocalStorageValue("agent_model_" + String(internalId), data.model, setModel); setLocalStorageArray("tool_names_" + String(internalId), data.tools, setToolNames); + setLocalStorageValue("is_agent_template_" + String(internalId), true, setShowButton); + setShowButton(true); }) .catch((error) => { console.error('Error fetching template details:', error); @@ -178,6 +202,10 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen setRollingDropdown(false) } + if (knowledgeRef.current && !knowledgeRef.current.contains(event.target)) { + setKnowledgeDropdown(false) + } + if (databaseRef.current && !databaseRef.current.contains(event.target)) { setDatabaseDropdown(false) } @@ -245,6 +273,12 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen }; + const handleKnowledgeSelect = (index) => { + setLocalStorageValue("agent_knowledge_" + String(internalId), knowledge[index].name, setSelectedKnowledge); + setLocalStorageValue("agent_knowledge_id_" + String(internalId), knowledge[index].id, setSelectedKnowledgeId); + setKnowledgeDropdown(false); + }; + const handleStepChange = (event) => { setLocalStorageValue("agent_step_time_" + String(internalId), event.target.value, setStepTime); }; @@ -322,13 +356,11 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen const handleDescriptionChange = (event) => { setLocalStorageValue("agent_description_" + String(internalId), event.target.value, setAgentDescription); }; + const closeCreateModal = () => { setCreateModal(false); setCreateDropdown(false); }; - const preventDefault = (e) => { - e.stopPropagation(); - }; function uploadResource(agentId, fileData) { const formData = new FormData(); @@ -364,36 +396,50 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen } }, [scheduleData]); - const handleAddAgent = () => { - if (!hasAPIkey) { + const validateAgentData = (isNewAgent) => { + if (isNewAgent && !hasAPIkey) { toast.error("Your OpenAI/Palm API key is empty!", {autoClose: 1800}); openNewTab(-3, "Settings", "Settings", false); - return + return false; } - if (agentName.replace(/\s/g, '') === '') { + if (agentName?.replace(/\s/g, '') === '') { toast.error("Agent name can't be blank", {autoClose: 1800}); - return + return false; } - if (agentDescription.replace(/\s/g, '') === '') { + if (agentDescription?.replace(/\s/g, '') === '') { toast.error("Agent description can't be blank", {autoClose: 1800}); - return + return false; } const isEmptyGoal = goals.some((goal) => goal.replace(/\s/g, '') === ''); if (isEmptyGoal) { toast.error("Goal can't be empty", {autoClose: 1800}); - return; + return false; } if (selectedTools.length <= 0) { toast.error("Add atleast one tool", {autoClose: 1800}); - return + return false; } - if(!modelsArray.includes(model)) { + + if (!modelsArray.includes(model)) { toast.error("Your key does not have access to the selected model", {autoClose: 1800}); - return + return false; + } + + if (toolNames.includes('Knowledge Search') && !selectedKnowledge) { + toast.error("Add atleast one knowledge", {autoClose: 1800}); + return; + } + + return true; + } + + const handleAddAgent = () => { + if (!validateAgentData(true)) { + return; } setCreateClickable(false); @@ -420,7 +466,9 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen "permission_type": permission_type, "LTM_DB": longTermMemory ? database : null, "user_timezone": getUserTimezone(), + "knowledge": toolNames.includes('Knowledge Search') ? selectedKnowledgeId : null, }; + const scheduleAgentData = { "agent_config": agentData, "schedule": scheduleData, @@ -537,6 +585,46 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen event.preventDefault(); }; + function updateTemplate() { + + if (!validateAgentData(false)) return; + + let permission_type = permission; + if (permission.includes("RESTRICTED")) { + permission_type = "RESTRICTED"; + } + + const agentTemplateConfigData = { + "goal": goals, + "instruction": instructions, + "agent_type": agentType, + "constraints": constraints, + "tools": toolNames, + "exit": exitCriterion, + "iteration_interval": stepTime, + "model": model, + "max_iterations": maxIterations, + "permission_type": permission_type, + "LTM_DB": longTermMemory ? database : null, + } + const editTemplateData = { + "name": agentName, + "description": agentDescription, + "agent_configs": agentTemplateConfigData + } + + editAgentTemplate(agentTemplateId, editTemplateData) + .then((response) => { + if (response.status === 200) { + toast.success('Agent template has been updated successfully!', {autoClose: 1800}); + } + }) + .catch((error) => { + toast.error("Error updating agent template") + console.error('Error updating agent template:', error); + }); + }; + function setFileData(files) { if (files.length > 0) { const fileData = { @@ -585,11 +673,21 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen setAdvancedOptions(JSON.parse(advanced_options)); } + const is_agent_template = localStorage.getItem("is_agent_template_" + String(internalId)); + if (is_agent_template) { + setShowButton(true); + } + const agent_name = localStorage.getItem("agent_name_" + String(internalId)); if (agent_name) { setAgentName(agent_name); } + const agent_template_id = localStorage.getItem("agent_template_id_" + String(internalId)); + if (agent_template_id) { + setAgentTemplateId(agent_template_id) + } + const agent_description = localStorage.getItem("agent_description_" + String(internalId)); if (agent_description) { setAgentDescription(agent_description); @@ -660,8 +758,18 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen setInput(JSON.parse(agent_files)); } } + + const agent_knowledge = localStorage.getItem("agent_knowledge_" + String(internalId)); + if (agent_knowledge) { + setSelectedKnowledge(agent_knowledge); + } }, [internalId]) + function openMarketplace() { + openNewTab(-4, "Marketplace", "Marketplace", false); + localStorage.setItem('marketplace_tab', 'market_knowledge'); + } + return (<>
@@ -691,7 +799,7 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen {goals.length > 1 &&
}
))} @@ -715,7 +823,7 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen {instructions.length > 1 &&
} ))} @@ -749,17 +857,26 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen
setToolkitDropdown(!toolkitDropdown)} style={{width: '100%', alignItems: 'flex-start'}}> - {toolNames && toolNames.length > 0 ?
- {toolNames.map((tool, index) => ( +
+ {toolNames && toolNames.length > 0 && toolNames.map((tool, index) => (
{tool}
close-icon removeTool(index)}/>
-
))} - setSearchValue(e.target.value)} onFocus={() => setToolkitDropdown(true)} +
+ ))} + setSearchValue(e.target.value)} + onFocus={() => { + setToolkitDropdown(true); + setShowPlaceholder(false); + }} onBlur={() => { + setShowPlaceholder(true); + }} onClick={(e) => e.stopPropagation()}/> -
:
Select Tools
} + {toolNames && toolNames.length === 0 && showPlaceholder && searchValue.length === 0 && +
Select Tools
} +
clearTools(e)} src='/images/clear_input.svg' alt="clear-input"/> @@ -812,6 +929,97 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen
+ {toolNames.includes("Knowledge Search") &&
+ +
+
setKnowledgeDropdown(!knowledgeDropdown)} + style={selectedKnowledge ? {width: '100%'} : {width: '100%', color: '#888888'}}> + {selectedKnowledge || 'Select knowledge'}expand-icon +
+
+ {knowledgeDropdown && knowledge && knowledge.length > 0 && +
+ {knowledge.map((item, index) => ( +
handleKnowledgeSelect(index)} + style={{padding: '12px 14px', maxWidth: '100%'}}> + {item.name} +
))} +
+
sendKnowledgeData({ + id: -6, + name: "new knowledge", + contentType: "Add_Knowledge", + internalId: createInternalId() + })}> + add-icon  Add + new knowledge +
+
+
+
+ marketplace  Browse knowledge from marketplace +
+
+
} + {knowledgeDropdown && knowledge && knowledge.length <= 0 && +
+
+ no-permissions + No knowledge found +
+
+
sendKnowledgeData({ + id: -6, + name: "new knowledge", + contentType: "Add_Knowledge", + internalId: createInternalId() + })}> + add-icon  Add + new knowledge +
+
+
+
+ marketplace  Browse knowledge from marketplace +
+
+
} +
+
+
}
removeFile(index)}>close-icon
@@ -891,7 +1099,7 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen } -
+
{constraints.map((constraint, index) => (
))} @@ -992,8 +1200,19 @@ export default function AgentCreate({sendAgentData, selectedProjectId, fetchAgen + {showButton && ( + + )}
- {createDropdown && (
{setCreateModal(true);setCreateDropdown(false);}}>Create & Schedule Run + {createDropdown && (
{ + setCreateModal(true); + setCreateDropdown(false); + }}>Create & Schedule Run
)}
diff --git a/gui/pages/Content/Agents/AgentSchedule.js b/gui/pages/Content/Agents/AgentSchedule.js index 9a476f1a2..e815ac3c2 100644 --- a/gui/pages/Content/Agents/AgentSchedule.js +++ b/gui/pages/Content/Agents/AgentSchedule.js @@ -1,5 +1,5 @@ import React, {useState, useEffect, useRef} from 'react'; -import {setLocalStorageValue, convertToGMT} from "@/utils/utils"; +import {setLocalStorageValue, convertToGMT, preventDefault} from "@/utils/utils"; import styles from "@/pages/Content/Agents/Agents.module.css"; import styles1 from "@/pages/Content/Agents/react-datetime.css"; import Image from "next/image"; @@ -9,7 +9,15 @@ import {agentScheduleComponent, createAndScheduleRun, updateSchedule} from "@/pa import {EventBus} from "@/utils/eventBus"; import moment from 'moment'; -export default function AgentSchedule({internalId, closeCreateModal, type, agentId, setCreateModal, setCreateEditModal, env}) { +export default function AgentSchedule({ + internalId, + closeCreateModal, + type, + agentId, + setCreateModal, + setCreateEditModal, + env + }) { const [isRecurring, setIsRecurring] = useState(false); const [timeDropdown, setTimeDropdown] = useState(false); const [expiryDropdown, setExpiryDropdown] = useState(false); @@ -129,10 +137,6 @@ export default function AgentSchedule({internalId, closeCreateModal, type, agent setLocalStorageValue("agent_expiry_runs_" + String(internalId), event.target.value, setExpiryRuns); }; - const preventDefault = (e) => { - e.stopPropagation(); - }; - const addScheduledAgent = () => { if ((startTime === '' || (isRecurring === true && (timeValue == null || (expiryType === "After certain number of runs" && (parseInt(expiryRuns, 10) < 1)) || (expiryType === "Specific date" && expiryDate == null))))) { toast.error('Please input correct details', {autoClose: 1800}); diff --git a/gui/pages/Content/Agents/AgentTemplatesList.js b/gui/pages/Content/Agents/AgentTemplatesList.js index dfec926da..0dedde4b2 100644 --- a/gui/pages/Content/Agents/AgentTemplatesList.js +++ b/gui/pages/Content/Agents/AgentTemplatesList.js @@ -5,7 +5,17 @@ import {fetchAgentTemplateListLocal} from "@/pages/api/DashboardService"; import AgentCreate from "@/pages/Content/Agents/AgentCreate"; import {setLocalStorageValue, openNewTab} from "@/utils/utils"; -export default function AgentTemplatesList({sendAgentData, selectedProjectId, fetchAgents, toolkits, organisationId, internalId, env}) { +export default function AgentTemplatesList({ + sendAgentData, + knowledge, + selectedProjectId, + fetchAgents, + toolkits, + organisationId, + internalId, + sendKnowledgeData, + env + }) { const [agentTemplates, setAgentTemplates] = useState([]) const [createAgentClicked, setCreateAgentClicked] = useState(false) const [sendTemplate, setSendTemplate] = useState(null) @@ -103,9 +113,10 @@ export default function AgentTemplatesList({sendAgentData, selectedProjectId, fe
}
-
: : } + template={sendTemplate} env={env}/>} ) }; diff --git a/gui/pages/Content/Agents/AgentWorkspace.js b/gui/pages/Content/Agents/AgentWorkspace.js index a9da61a41..c59d8fa76 100644 --- a/gui/pages/Content/Agents/AgentWorkspace.js +++ b/gui/pages/Content/Agents/AgentWorkspace.js @@ -9,6 +9,7 @@ import RunHistory from "./RunHistory"; import ActionConsole from "./ActionConsole"; import Details from "./Details"; import ResourceManager from "./ResourceManager"; +import {preventDefault} from "@/utils/utils"; import { getAgentDetails, getAgentExecutions, @@ -26,7 +27,7 @@ import AgentSchedule from "@/pages/Content/Agents/AgentSchedule"; export default function AgentWorkspace({env, agentId, agentName, selectedView, agents, internalId}) { const [leftPanel, setLeftPanel] = useState('activity_feed') - const [rightPanel, setRightPanel] = useState('') + const [rightPanel, setRightPanel] = useState('details') const [history, setHistory] = useState(true) const [selectedRun, setSelectedRun] = useState(null) const [runModal, setRunModal] = useState(false) @@ -40,7 +41,7 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a const [fetchedData, setFetchedData] = useState(null); const [instructions, setInstructions] = useState(['']); const [currentInstructions, setCurrentInstructions] = useState(['']); - const [pendingPermission, setPendingPermissions] = useState(0) + const [pendingPermission, setPendingPermissions] = useState(0); const agent = agents.find(agent => agent.id === agentId); @@ -57,11 +58,13 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a const handleEditScheduleClick = () => { setCreateEditModal(true); + setDropdown(false); }; const handleStopScheduleClick = () => { setCreateStopModal(true); setCreateModal(false); + setDropdown(false); }; function fetchStopSchedule() {//Stop Schedule @@ -157,16 +160,22 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a setDeleteModal(false); if (response.status === 200) { EventBus.emit('reFetchAgents', {}); - EventBus.emit('removeTab',{element: {id: agentId, name: agentName, contentType: "Agents", internalId: internalId}}) + EventBus.emit('removeTab', { + element: { + id: agentId, + name: agentName, + contentType: "Agents", + internalId: internalId + } + }) toast.success("Agent Deleted Successfully", {autoClose: 1800}); - } - else{ - toast.error("Agent Could not be Deleted", { autoClose: 1800 }); + } else { + toast.error("Agent Could not be Deleted", {autoClose: 1800}); } }) .catch((error) => { setDeleteModal(false); - toast.error("Agent Could not be Deleted", { autoClose: 1800 }); + toast.error("Agent Could not be Deleted", {autoClose: 1800}); console.error("Agent could not be deleted: ", error); }); } @@ -199,10 +208,6 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a setDropdown(false); }; - const preventDefault = (e) => { - e.stopPropagation(); - }; - useEffect(() => { fetchAgentDetails(agentId); fetchExecutions(agentId); @@ -239,7 +244,7 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a console.error('Error fetching agent data:', error); }); } - }; + } function fetchExecutions(agentId, currentRun = null) { getAgentExecutions(agentId) @@ -275,6 +280,8 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a .catch((error) => { console.error('Error saving agent as template:', error); }); + + setDropdown(false); } useEffect(() => { @@ -344,10 +351,10 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a run-icon New Run - {} + {dropdown &&
setDropdown(true)} onMouseLeave={() => setDropdown(false)}>
  • saveAgentTemplate()}>Save as Template
  • @@ -367,9 +374,16 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a
  • Stop Schedule
) : (
{agent && !agent?.is_running && !agent?.is_scheduled && -
  • setCreateModal(true)}>Schedule Run
  • } +
  • { + setDropdown(false); + setCreateModal(true) + }}>Schedule Run
  • }
    )} -
  • setDeleteModal(true)}>Delete Agent
  • +
  • { + setDropdown(false); + setDeleteModal(true) + }}>Delete Agent +
  • } @@ -377,7 +391,8 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a setCreateModal(false)}/>} {createEditModal && - setCreateEditModal(false)}/>} {createStopModal && (
    @@ -467,7 +482,6 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a
    - {runModal && (
    Run agent name
    @@ -488,7 +502,7 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a {goals.length > 1 &&
    }
    ))} @@ -511,7 +525,7 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a {instructions.length > 1 &&
    }
    ))} @@ -532,15 +546,16 @@ export default function AgentWorkspace({env, agentId, agentName, selectedView, a )} {deleteModal && (
    -
    +
    - +
    -
    -
    )} -
    ); -} +} \ No newline at end of file diff --git a/gui/pages/Content/Agents/Details.js b/gui/pages/Content/Agents/Details.js index 2a5c66f48..884dc27c1 100644 --- a/gui/pages/Content/Agents/Details.js +++ b/gui/pages/Content/Agents/Details.js @@ -11,6 +11,22 @@ export default function Details({agentDetails, runCount, goals, instructions, ag const [filteredInstructions, setFilteredInstructions] = useState(false); const [scheduleText, setScheduleText] = useState('Agent is not Scheduled'); + const info_text = { + marginLeft: '7px', + }; + + const info_text_secondary = { + marginLeft: '3px', + marginTop: '2px', + color: '#888888', + lineHeight: '13px', + fontSize: '11px' + }; + + const openToolkitTab = (toolId) => { + EventBus.emit('openToolkitTab', {toolId: toolId}); + } + useEffect(() => { setFilteredInstructions(instructions?.filter(instruction => instruction.trim() !== '')); }, [instructions]); @@ -46,22 +62,6 @@ export default function Details({agentDetails, runCount, goals, instructions, ag } }, [agentScheduleDetails]); - const info_text = { - marginLeft: '7px', - }; - - const info_text_secondary = { - marginLeft: '3px', - marginTop: '2px', - color: '#888888', - lineHeight: '13px', - fontSize: '11px' - }; - - const openToolkitTab = (toolId) => { - EventBus.emit('openToolkitTab', {toolId: toolId}); - } - return (<>
    {agentDetails?.name || ''}
    @@ -162,6 +162,10 @@ export default function Details({agentDetails, runCount, goals, instructions, ag
    queue-icon
    {agentDetails?.agent_type || ''}
    + {agentDetails?.knowledge_name &&
    +
    book-icon
    +
    {agentDetails?.knowledge_name}
    +
    }
    model-icon
    {agentDetails?.model || ''}
    @@ -170,10 +174,10 @@ export default function Details({agentDetails, runCount, goals, instructions, ag {/*
    exit-icon
    */} {/*
    {exit}
    */} {/*
    */} -
    + {/*
    window-icon
    {agentDetails?.memory_window || 0} milliseconds
    -
    +
    */}
    permission-type-icon
    {agentDetails?.permission_type.replace(/\s*\([^)]*\)/g, '') || ''}
    diff --git a/gui/pages/Content/Agents/ResourceList.js b/gui/pages/Content/Agents/ResourceList.js index 0207211ce..45e65f8e9 100644 --- a/gui/pages/Content/Agents/ResourceList.js +++ b/gui/pages/Content/Agents/ResourceList.js @@ -34,8 +34,8 @@ export default function ResourceList({files, channel, runs}) { ) return ( -
    - {channel === 'output' && (!isAnyFileWithAgentId || files.length <= 0 ? +
    + {channel === 'output' && (!isAnyFileWithAgentId || files.length <= 0 ?
    no-permissions No Output files! @@ -43,30 +43,30 @@ export default function ResourceList({files, channel, runs}) { :
    {filesByRun.map((filesRun, index) => ( -
    -
    setSelectedRunId(filesRun.run.id === selectedRunId ? null : filesRun.run.id)}> -
    - arrow - {filesRun.run.name} -
    bolt Run {filesByRun.length - index}
    -
    - download_icon downloadRunFiles(filesRun.run.id, filesRun.run.name)}/> -
    +
    +
    setSelectedRunId(filesRun.run.id === selectedRunId ? null : filesRun.run.id)}> +
    + arrow + {filesRun.run.name} +
    bolt Run {filesByRun.length - index}
    +
    + download_icon downloadRunFiles(filesRun.run.id, filesRun.run.name)}/> +
    - {selectedRunId === filesRun.run.id && ( -
    - {filesRun.files.map((file, index) => )} + {selectedRunId === filesRun.run.id && ( +
    + {filesRun.files.map((file, index) => )} +
    + )}
    - )} + ))}
    - ))} -
    )} {channel === 'input' && diff --git a/gui/pages/Content/Agents/ResourceManager.js b/gui/pages/Content/Agents/ResourceManager.js index f3acd2743..af60a61a7 100644 --- a/gui/pages/Content/Agents/ResourceManager.js +++ b/gui/pages/Content/Agents/ResourceManager.js @@ -16,7 +16,7 @@ export default function ResourceManager({agentId, runs}) { function handleFile(files) { if (files.length > 0) { - const sizeInMB = files[0].size / (1024*1024); + const sizeInMB = files[0].size / (1024 * 1024); if (sizeInMB > 5) { toast.error('File size should not exceed 5MB', {autoClose: 1800}); } else { diff --git a/gui/pages/Content/Knowledge/AddKnowledge.js b/gui/pages/Content/Knowledge/AddKnowledge.js new file mode 100644 index 000000000..98a6abd90 --- /dev/null +++ b/gui/pages/Content/Knowledge/AddKnowledge.js @@ -0,0 +1,46 @@ +import React, {useState, useEffect} from 'react'; +import KnowledgeForm from "@/pages/Content/Knowledge/KnowledgeForm"; + +export default function AddKnowledge({internalId, sendKnowledgeData}) { + const [knowledgeName, setKnowledgeName] = useState(''); + const [knowledgeDescription, setKnowledgeDescription] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(null); + + useEffect(() => { + const knowledge_name = localStorage.getItem("knowledge_name_" + String(internalId)) + if (knowledge_name) { + setKnowledgeName(knowledge_name); + } + + const knowledge_description = localStorage.getItem("knowledge_description_" + String(internalId)) + if (knowledge_description) { + setKnowledgeDescription(knowledge_description); + } + + const knowledge_index = localStorage.getItem("knowledge_index_" + String(internalId)) + if (knowledge_index) { + setSelectedIndex(JSON.parse(knowledge_index)); + } + }, [internalId]) + + return (<> +
    +
    +
    + +
    +
    +
    + ) +} \ No newline at end of file diff --git a/gui/pages/Content/Knowledge/Knowledge.js b/gui/pages/Content/Knowledge/Knowledge.js new file mode 100644 index 000000000..ab7518694 --- /dev/null +++ b/gui/pages/Content/Knowledge/Knowledge.js @@ -0,0 +1,61 @@ +import React from 'react'; +import Image from "next/image"; +import styles from '../Toolkits/Tool.module.css'; +import styles1 from '../Agents/Agents.module.css' +import {createInternalId} from "@/utils/utils"; + +export default function Knowledge({sendKnowledgeData, knowledge}) { + return ( + <> +
    +
    +

    Knowledges

    +
    +
    + +
    + + {knowledge && knowledge.length > 0 ? ( +
    +
    + {knowledge.map((item, index) => ( +
    sendKnowledgeData({ + id: item.id, + name: item.name, + contentType: "Knowledge", + internalId: createInternalId() + })}> +
    +
    +
    +
    +
    {item.name} {item.is_marketplace && + markteplace-icon}
    +
    by {item.contributed_by}
    +
    +
    +
    +
    +
    ) + )} +
    +
    + ) : ( +
    + No Knowledge found +
    + )} +
    + + ); +} \ No newline at end of file diff --git a/gui/pages/Content/Knowledge/Knowledge.module.css b/gui/pages/Content/Knowledge/Knowledge.module.css new file mode 100644 index 000000000..7768fb5c3 --- /dev/null +++ b/gui/pages/Content/Knowledge/Knowledge.module.css @@ -0,0 +1,76 @@ +.knowledge_label { + margin-bottom: 4px; + font-size: 12px; + color: #888888; +} + +.knowledge_info { + font-size: 12px; + color: white; +} + +.knowledge_info_box { + margin-bottom: 20px; +} + +.knowledge_wrapper { + margin-bottom: 20px; + display: flex; + justify-content: space-between; +} + +.knowledge_db { + font-size: 12px; + color: #888888; + font-weight: normal; + height: auto; + max-width: 240px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.knowledge_db_name { + padding:12px 14px; + border-top: 1px solid #888888; +} + +.knowledge_alert { + border-radius: 8px; + background-color: #423D52; + border-left: 4px solid #B3B2BB; + color: white; + font-size: 12px; + padding: 12px 14px; + display: flex; + justify-content: flex-start; +} + +.database_container { + background-color: rgb(39, 35, 53); + width: calc(33% - 10px); + padding: 10px; + color: white; + font-style: normal; + font-weight: 400; + font-size: 15px; + border-radius: 8px; + cursor: pointer; + text-align: center; +} + +.database_wrapper { + display: flex; + justify-content: flex-start; + gap: 10px; + flex-wrap: wrap; +} +.installed_knowledge_card_class { + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.14); + display: flex; + padding: 4px 8px; + align-items: center; + gap: 6px; +} \ No newline at end of file diff --git a/gui/pages/Content/Knowledge/KnowledgeDetails.js b/gui/pages/Content/Knowledge/KnowledgeDetails.js new file mode 100644 index 000000000..51f237d81 --- /dev/null +++ b/gui/pages/Content/Knowledge/KnowledgeDetails.js @@ -0,0 +1,209 @@ +import React, {useEffect, useState} from 'react'; +import styles1 from './Knowledge.module.css' +import {ToastContainer, toast} from "react-toastify"; +import styles from "@/pages/Content/Toolkits/Tool.module.css"; +import Image from "next/image"; +import KnowledgeForm from "@/pages/Content/Knowledge/KnowledgeForm"; +import {deleteCustomKnowledge, deleteMarketplaceKnowledge, getKnowledgeDetails} from "@/pages/api/DashboardService"; +import {removeTab} from "@/utils/utils"; +import {EventBus} from "@/utils/eventBus"; + +export default function KnowledgeDetails({internalId, knowledgeId}) { + const [showDescription, setShowDescription] = useState(false); + const [dropdown, setDropdown] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [knowledgeName, setKnowledgeName] = useState(''); + const [knowledgeDescription, setKnowledgeDescription] = useState(''); + const [installationType, setInstallationType] = useState(''); + const [model, setModel] = useState(''); + const [tokenizer, setTokenizer] = useState(''); + const [chunkSize, setChunkSize] = useState(''); + const [vectorDatabase, setVectorDatabase] = useState(''); + const [knowledgeDatatype, setKnowledgeDatatype] = useState(''); + const [textSplitters, setTextSplitters] = useState(''); + const [chunkOverlap, setChunkOverlap] = useState(''); + const [dimension, setDimension] = useState(''); + const [vectorDBIndex, setVectorDBIndex] = useState(''); + + const uninstallKnowledge = () => { + setDropdown(false); + + if (installationType === 'Marketplace') { + deleteMarketplaceKnowledge(knowledgeName) + .then((response) => { + console.log(response) + toast.success("Knowledge uninstalled successfully", {autoClose: 1800}); + removeTab(knowledgeId, knowledgeName, "Knowledge", internalId); + EventBus.emit('reFetchKnowledge', {}); + }) + .catch((error) => { + toast.error("Unable to uninstall knowledge", {autoClose: 1800}); + console.error('Error uninstalling knowledge:', error); + }); + } else { + deleteCustomKnowledge(knowledgeId) + .then((response) => { + toast.success("Knowledge uninstalled successfully", {autoClose: 1800}); + removeTab(knowledgeId, knowledgeName, "Knowledge", internalId); + EventBus.emit('reFetchKnowledge', {}); + }) + .catch((error) => { + toast.error("Unable to uninstall knowledge", {autoClose: 1800}); + console.error('Error uninstalling knowledge:', error); + }); + } + } + + const viewKnowledge = () => { + setDropdown(false); + } + + const editKnowledge = () => { + setIsEditing(true); + setDropdown(false); + } + + useEffect(() => { + if (knowledgeId) { + getKnowledgeDetails(knowledgeId) + .then((response) => { + const data = response.data || []; + setKnowledgeName(data.name); + setKnowledgeDescription(data.description); + setInstallationType(data.installation_type); + setModel(data.model); + setTokenizer(data.tokenizer); + setChunkSize(data.chunk_size); + setVectorDatabase(data.vector_database); + setKnowledgeDatatype(data.data_type); + setTextSplitters(data.text_splitter); + setChunkOverlap(data.chunk_overlap); + setDimension(data.dimensions); + setVectorDBIndex(data.vector_database_index); + }) + .catch((error) => { + console.error('Error fetching knowledge details:', error); + }); + } + }, [internalId]); + + return (<> +
    +
    +
    + {isEditing ? + : +
    +
    +
    +
    +
    +
    {knowledgeName}
    +
    + {`${showDescription ? knowledgeDescription : knowledgeDescription.slice(0, 70)}`} + {knowledgeDescription.length > 70 && + setShowDescription(!showDescription)}> + {showDescription ? '...less' : '...more'} + } +
    +
    +
    +
    + + {dropdown &&
    setDropdown(true)} onMouseLeave={() => setDropdown(false)}> +
      + {installationType !== 'Marketplace' && + //
    • View in marketplace
    • : +
    • Edit details
    • } +
    • Uninstall knowledge
    • +
    +
    } +
    +
    +
    + {installationType === 'Marketplace' &&
    +
    +
    + +
    {installationType}
    +
    +
    + +
    {model}
    +
    +
    + +
    {tokenizer}
    +
    +
    + +
    {chunkSize}
    +
    +
    + +
    {vectorDatabase}
    +
    +
    +
    +
    + +
    {knowledgeDatatype}
    +
    +
    + +
    {textSplitters}
    +
    +
    + +
    {chunkOverlap}
    +
    +
    + +
    {dimension}
    +
    +
    + +
    {vectorDBIndex?.name || ''}
    +
    +
    +
    } + {installationType === 'Custom' &&
    +
    +
    + +
    {installationType}
    +
    +
    + +
    {vectorDBIndex?.name || ''}
    +
    +
    +
    +
    + +
    {vectorDatabase}
    +
    +
    +
    } +
    } +
    +
    +
    + + ); +} \ No newline at end of file diff --git a/gui/pages/Content/Knowledge/KnowledgeForm.js b/gui/pages/Content/Knowledge/KnowledgeForm.js new file mode 100644 index 000000000..f6b0e2d7a --- /dev/null +++ b/gui/pages/Content/Knowledge/KnowledgeForm.js @@ -0,0 +1,244 @@ +import React, {useState, useEffect, useRef} from 'react'; +import styles1 from '@/pages/Content/Knowledge/Knowledge.module.css' +import {removeTab, setLocalStorageValue, setLocalStorageArray, createInternalId} from "@/utils/utils"; +import styles from "@/pages/Content/Agents/Agents.module.css"; +import Image from "next/image"; +import {ToastContainer, toast} from "react-toastify"; +import {addUpdateKnowledge, getValidIndices} from "@/pages/api/DashboardService"; +import {EventBus} from "@/utils/eventBus"; + +export default function KnowledgeForm({ + internalId, + knowledgeId, + knowledgeName, + setKnowledgeName, + knowledgeDescription, + setKnowledgeDescription, + selectedIndex, + setSelectedIndex, + isEditing, + setIsEditing, + sendKnowledgeData + }) { + const [addClickable, setAddClickable] = useState(true); + const indexRef = useRef(null); + const [indexDropdown, setIndexDropdown] = useState(false); + const [pinconeIndices, setPineconeIndices] = useState([]); + const [qdrantIndices, setQdrantIndices] = useState([]); + + useEffect(() => { + getValidIndices() + .then((response) => { + const data = response.data || []; + if (data) { + setPineconeIndices(data.pinecone || []); + setQdrantIndices(data.qdrant || []); + } + }) + .catch((error) => { + console.error('Error fetching indices:', error); + }); + }, []); + + useEffect(() => { + function handleClickOutside(event) { + if (indexRef.current && !indexRef.current.contains(event.target)) { + setIndexDropdown(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const handleNameChange = (event) => { + setLocalStorageValue("knowledge_name_" + String(internalId), event.target.value, setKnowledgeName); + }; + + const handleDescriptionChange = (event) => { + setLocalStorageValue("knowledge_description_" + String(internalId), event.target.value, setKnowledgeDescription); + }; + + function validationCheck() { + let isValid = true; + + if (knowledgeName.replace(/\s/g, '') === '') { + toast.error("Knowledge name can't be blank", {autoClose: 1800}); + isValid = false; + } + + if (!selectedIndex) { + toast.error("Please select an index", {autoClose: 1800}); + isValid = false; + } + + return isValid; + } + + const handleAddKnowledge = () => { + if (!validationCheck()) { + return + } + + const knowledgeData = { + "id": 0, + "name": knowledgeName, + "description": knowledgeDescription, + "index_id": selectedIndex.id + } + + addUpdateKnowledge(knowledgeData) + .then((response) => { + toast.success("Knowledge added successfully", {autoClose: 1800}); + sendKnowledgeData({ + id: response.data.id, + name: knowledgeName, + contentType: "Knowledge", + internalId: createInternalId() + }); + EventBus.emit('reFetchKnowledge', {}); + }) + .catch((error) => { + toast.error("Unable to add knowledge", {autoClose: 1800}); + console.error('Error deleting knowledge:', error); + }); + + setAddClickable(false); + } + + const handleUpdateKnowledge = () => { + if (!validationCheck()) { + return + } + + const knowledgeData = { + "id": knowledgeId, + "name": knowledgeName, + "description": knowledgeDescription, + "index_id": selectedIndex.id + } + + addUpdateKnowledge(knowledgeData) + .then((response) => { + toast.success("Knowledge updated successfully", {autoClose: 1800}); + EventBus.emit('reFetchKnowledge', {}); + }) + .catch((error) => { + toast.error("Unable to update knowledge", {autoClose: 1800}); + console.error('Error deleting knowledge:', error); + }); + + setIsEditing(false); + setAddClickable(false); + } + + const handleIndexSelect = (index) => { + setLocalStorageArray("knowledge_index_" + String(internalId), index, setSelectedIndex); + setIndexDropdown(false); + } + + const checkIndexValidity = (validState) => { + let errorMessage = ""; + let isValid = true; + + if (!validState) { + isValid = false; + errorMessage = "The configured index is either empty or has marketplace knowledge"; + } + + return [isValid, errorMessage]; + } + + return (<> +
    +
    {isEditing ? 'Edit knowledge' : 'Add a new knowledge'}
    +
    +
    +
    +
    + info-icon +
    +
    + Currently we support Open AI “text-knowledge-ada-002” model knowledge only. Please make sure you add the same. +
    +
    +
    +
    +
    + + +
    +
    +
    + +