mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
725 lines
28 KiB
TypeScript
725 lines
28 KiB
TypeScript
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';
|
|
|
|
import userService from '../api/services/userService';
|
|
import ArrowLeft from '../assets/arrow-left.svg';
|
|
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';
|
|
import {
|
|
selectSelectedAgent,
|
|
selectSourceDocs,
|
|
selectToken,
|
|
setSelectedAgent,
|
|
} from '../preferences/preferenceSlice';
|
|
import PromptsModal from '../preferences/PromptsModal';
|
|
import { UserToolType } from '../settings/types';
|
|
import AgentPreview from './AgentPreview';
|
|
import { Agent } from './types';
|
|
|
|
const embeddingsName =
|
|
import.meta.env.VITE_EMBEDDINGS_NAME ||
|
|
'huggingface_sentence-transformers/all-mpnet-base-v2';
|
|
|
|
export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
|
const navigate = useNavigate();
|
|
const dispatch = useDispatch();
|
|
const { agentId } = useParams();
|
|
|
|
const token = useSelector(selectToken);
|
|
const sourceDocs = useSelector(selectSourceDocs);
|
|
const selectedAgent = useSelector(selectSelectedAgent);
|
|
|
|
const [effectiveMode, setEffectiveMode] = useState(mode);
|
|
const [agent, setAgent] = useState<Agent>({
|
|
id: agentId || '',
|
|
name: '',
|
|
description: '',
|
|
image: '',
|
|
source: '',
|
|
chunks: '',
|
|
retriever: '',
|
|
prompt_id: '',
|
|
tools: [],
|
|
agent_type: '',
|
|
status: '',
|
|
});
|
|
const [imageFile, setImageFile] = useState<File | null>(null);
|
|
const [prompts, setPrompts] = useState<
|
|
{ name: string; id: string; type: string }[]
|
|
>([]);
|
|
const [userTools, setUserTools] = useState<OptionType[]>([]);
|
|
const [isSourcePopupOpen, setIsSourcePopupOpen] = useState(false);
|
|
const [isToolsPopupOpen, setIsToolsPopupOpen] = useState(false);
|
|
const [selectedSourceIds, setSelectedSourceIds] = useState<
|
|
Set<string | number>
|
|
>(new Set());
|
|
const [selectedToolIds, setSelectedToolIds] = useState<Set<string | number>>(
|
|
new Set(),
|
|
);
|
|
const [deleteConfirmation, setDeleteConfirmation] =
|
|
useState<ActiveState>('INACTIVE');
|
|
const [agentDetails, setAgentDetails] = useState<ActiveState>('INACTIVE');
|
|
const [addPromptModal, setAddPromptModal] = useState<ActiveState>('INACTIVE');
|
|
const [hasChanges, setHasChanges] = useState(false);
|
|
const [draftLoading, setDraftLoading] = useState(false);
|
|
const [publishLoading, setPublishLoading] = useState(false);
|
|
|
|
const initialAgentRef = useRef<Agent | null>(null);
|
|
const sourceAnchorButtonRef = useRef<HTMLButtonElement>(null);
|
|
const toolAnchorButtonRef = useRef<HTMLButtonElement>(null);
|
|
|
|
const modeConfig = {
|
|
new: {
|
|
heading: 'New Agent',
|
|
buttonText: 'Publish',
|
|
showDelete: false,
|
|
showSaveDraft: true,
|
|
showLogs: false,
|
|
showAccessDetails: false,
|
|
trackChanges: false,
|
|
},
|
|
edit: {
|
|
heading: 'Edit Agent',
|
|
buttonText: 'Save',
|
|
showDelete: true,
|
|
showSaveDraft: false,
|
|
showLogs: true,
|
|
showAccessDetails: true,
|
|
trackChanges: true,
|
|
},
|
|
draft: {
|
|
heading: 'New Agent (Draft)',
|
|
buttonText: 'Publish',
|
|
showDelete: true,
|
|
showSaveDraft: true,
|
|
showLogs: false,
|
|
showAccessDetails: false,
|
|
trackChanges: false,
|
|
},
|
|
};
|
|
const chunks = ['0', '2', '4', '6', '8', '10'];
|
|
const agentTypes = [
|
|
{ label: 'Classic', value: 'classic' },
|
|
{ label: 'ReAct', value: 'react' },
|
|
];
|
|
|
|
const isPublishable = () => {
|
|
return (
|
|
agent.name && agent.description && agent.prompt_id && agent.agent_type
|
|
);
|
|
};
|
|
|
|
const handleUpload = useCallback((files: File[]) => {
|
|
if (files && files.length > 0) {
|
|
const file = files[0];
|
|
setImageFile(file);
|
|
}
|
|
}, []);
|
|
|
|
const handleCancel = () => {
|
|
if (selectedAgent) dispatch(setSelectedAgent(null));
|
|
navigate('/agents');
|
|
};
|
|
|
|
const handleDelete = async (agentId: string) => {
|
|
const response = await userService.deleteAgent(agentId, token);
|
|
if (!response.ok) throw new Error('Failed to delete agent');
|
|
navigate('/agents');
|
|
};
|
|
|
|
const handleSaveDraft = async () => {
|
|
const formData = new FormData();
|
|
formData.append('name', agent.name);
|
|
formData.append('description', agent.description);
|
|
formData.append('source', agent.source);
|
|
formData.append('chunks', agent.chunks);
|
|
formData.append('retriever', agent.retriever);
|
|
formData.append('prompt_id', agent.prompt_id);
|
|
formData.append('agent_type', agent.agent_type);
|
|
formData.append('status', 'draft');
|
|
|
|
if (imageFile) formData.append('image', imageFile);
|
|
|
|
if (agent.tools && agent.tools.length > 0)
|
|
formData.append('tools', JSON.stringify(agent.tools));
|
|
else formData.append('tools', '[]');
|
|
|
|
try {
|
|
setDraftLoading(true);
|
|
const response =
|
|
effectiveMode === 'new'
|
|
? await userService.createAgent(formData, token)
|
|
: await userService.updateAgent(agent.id || '', formData, token);
|
|
if (!response.ok) throw new Error('Failed to create agent draft');
|
|
const data = await response.json();
|
|
|
|
const updatedAgent = {
|
|
...agent,
|
|
id: data.id || agent.id,
|
|
image: data.image || agent.image,
|
|
};
|
|
setAgent(updatedAgent);
|
|
|
|
if (effectiveMode === 'new') setEffectiveMode('draft');
|
|
} catch (error) {
|
|
console.error('Error saving draft:', error);
|
|
throw new Error('Failed to save draft');
|
|
} finally {
|
|
setDraftLoading(false);
|
|
}
|
|
};
|
|
|
|
const handlePublish = async () => {
|
|
const formData = new FormData();
|
|
formData.append('name', agent.name);
|
|
formData.append('description', agent.description);
|
|
formData.append('source', agent.source);
|
|
formData.append('chunks', agent.chunks);
|
|
formData.append('retriever', agent.retriever);
|
|
formData.append('prompt_id', agent.prompt_id);
|
|
formData.append('agent_type', agent.agent_type);
|
|
formData.append('status', 'published');
|
|
|
|
if (imageFile) formData.append('image', imageFile);
|
|
if (agent.tools && agent.tools.length > 0)
|
|
formData.append('tools', JSON.stringify(agent.tools));
|
|
else formData.append('tools', '[]');
|
|
|
|
try {
|
|
setPublishLoading(true);
|
|
const response =
|
|
effectiveMode === 'new'
|
|
? await userService.createAgent(formData, token)
|
|
: await userService.updateAgent(agent.id || '', formData, token);
|
|
if (!response.ok) throw new Error('Failed to publish agent');
|
|
const data = await response.json();
|
|
|
|
const updatedAgent = {
|
|
...agent,
|
|
id: data.id || agent.id,
|
|
key: data.key || agent.key,
|
|
status: 'published',
|
|
image: data.image || agent.image,
|
|
};
|
|
setAgent(updatedAgent);
|
|
initialAgentRef.current = updatedAgent;
|
|
|
|
if (effectiveMode === 'new' || effectiveMode === 'draft') {
|
|
setEffectiveMode('edit');
|
|
setAgentDetails('ACTIVE');
|
|
}
|
|
setImageFile(null);
|
|
} catch (error) {
|
|
console.error('Error publishing agent:', error);
|
|
throw new Error('Failed to publish agent');
|
|
} finally {
|
|
setPublishLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const getTools = async () => {
|
|
const response = await userService.getUserTools(token);
|
|
if (!response.ok) throw new Error('Failed to fetch tools');
|
|
const data = await response.json();
|
|
const tools: OptionType[] = data.tools.map((tool: UserToolType) => ({
|
|
id: tool.id,
|
|
label: tool.displayName,
|
|
icon: `/toolIcons/tool_${tool.name}.svg`,
|
|
}));
|
|
setUserTools(tools);
|
|
};
|
|
const getPrompts = async () => {
|
|
const response = await userService.getPrompts(token);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch prompts');
|
|
}
|
|
const data = await response.json();
|
|
setPrompts(data);
|
|
};
|
|
getTools();
|
|
getPrompts();
|
|
}, [token]);
|
|
|
|
useEffect(() => {
|
|
if ((mode === 'edit' || mode === 'draft') && agentId) {
|
|
const getAgent = async () => {
|
|
const response = await userService.getAgent(agentId, token);
|
|
if (!response.ok) {
|
|
navigate('/agents');
|
|
throw new Error('Failed to fetch agent');
|
|
}
|
|
const data = await response.json();
|
|
if (data.source) setSelectedSourceIds(new Set([data.source]));
|
|
else if (data.retriever)
|
|
setSelectedSourceIds(new Set([data.retriever]));
|
|
if (data.tools) setSelectedToolIds(new Set(data.tools));
|
|
if (data.status === 'draft') setEffectiveMode('draft');
|
|
setAgent(data);
|
|
initialAgentRef.current = data;
|
|
};
|
|
getAgent();
|
|
}
|
|
}, [agentId, mode, token]);
|
|
|
|
useEffect(() => {
|
|
const selectedSource = Array.from(selectedSourceIds).map((id) =>
|
|
sourceDocs?.find(
|
|
(source) =>
|
|
source.id === id || source.retriever === id || source.name === id,
|
|
),
|
|
);
|
|
if (selectedSource[0]?.model === embeddingsName) {
|
|
if (selectedSource[0] && 'id' in selectedSource[0]) {
|
|
setAgent((prev) => ({
|
|
...prev,
|
|
source: selectedSource[0]?.id || 'default',
|
|
retriever: '',
|
|
}));
|
|
} else
|
|
setAgent((prev) => ({
|
|
...prev,
|
|
source: '',
|
|
retriever: selectedSource[0]?.retriever || 'classic',
|
|
}));
|
|
}
|
|
}, [selectedSourceIds]);
|
|
|
|
useEffect(() => {
|
|
const selectedTool = Array.from(selectedToolIds).map((id) =>
|
|
userTools.find((tool) => tool.id === id),
|
|
);
|
|
setAgent((prev) => ({
|
|
...prev,
|
|
tools: selectedTool
|
|
.map((tool) => tool?.id)
|
|
.filter((id): id is string => typeof id === 'string'),
|
|
}));
|
|
}, [selectedToolIds]);
|
|
|
|
useEffect(() => {
|
|
if (isPublishable()) dispatch(setSelectedAgent(agent));
|
|
|
|
if (!modeConfig[effectiveMode].trackChanges) {
|
|
setHasChanges(true);
|
|
return;
|
|
}
|
|
if (!initialAgentRef.current) {
|
|
setHasChanges(false);
|
|
return;
|
|
}
|
|
const isChanged =
|
|
!isEqual(agent, initialAgentRef.current) || imageFile !== null;
|
|
setHasChanges(isChanged);
|
|
}, [agent, dispatch, effectiveMode, imageFile]);
|
|
return (
|
|
<div className="p-4 md:p-12">
|
|
<div className="flex items-center gap-3 px-4">
|
|
<button
|
|
className="rounded-full border p-3 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
|
|
onClick={handleCancel}
|
|
>
|
|
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
|
</button>
|
|
<p className="text-eerie-black dark:text-bright-gray mt-px text-sm font-semibold">
|
|
Back to all agents
|
|
</p>
|
|
</div>
|
|
<div className="mt-5 flex w-full flex-wrap items-center justify-between gap-2 px-4">
|
|
<h1 className="text-eerie-black m-0 text-[40px] font-bold dark:text-white">
|
|
{modeConfig[effectiveMode].heading}
|
|
</h1>
|
|
<div className="flex flex-wrap items-center gap-1">
|
|
<button
|
|
className="text-purple-30 dark:text-light-gray mr-4 rounded-3xl py-2 text-sm font-medium dark:bg-transparent"
|
|
onClick={handleCancel}
|
|
>
|
|
Cancel
|
|
</button>
|
|
{modeConfig[effectiveMode].showDelete && agent.id && (
|
|
<button
|
|
className="group border-red-2000 text-red-2000 hover:bg-red-2000 flex items-center gap-2 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
|
onClick={() => setDeleteConfirmation('ACTIVE')}
|
|
>
|
|
<span className="block h-4 w-4 bg-[url('/src/assets/red-trash.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/white-trash.svg')]" />
|
|
Delete
|
|
</button>
|
|
)}
|
|
{modeConfig[effectiveMode].showSaveDraft && (
|
|
<button
|
|
className="hover:bg-vi</button>olets-are-blue border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue w-28 rounded-3xl border border-solid py-2 text-sm font-medium transition-colors hover:text-white"
|
|
onClick={handleSaveDraft}
|
|
>
|
|
<span className="flex items-center justify-center transition-all duration-200">
|
|
{draftLoading ? (
|
|
<Spinner size="small" color="#976af3" />
|
|
) : (
|
|
'Save Draft'
|
|
)}
|
|
</span>
|
|
</button>
|
|
)}
|
|
{modeConfig[effectiveMode].showAccessDetails && (
|
|
<button
|
|
className="group border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue flex items-center gap-2 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
|
onClick={() => navigate(`/agents/logs/${agent.id}`)}
|
|
>
|
|
<span className="block h-5 w-5 bg-[url('/src/assets/monitoring-purple.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/monitoring-white.svg')]" />
|
|
Logs
|
|
</button>
|
|
)}
|
|
{modeConfig[effectiveMode].showAccessDetails && (
|
|
<button
|
|
className="hover:bg-vi</button>olets-are-blue border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
|
onClick={() => setAgentDetails('ACTIVE')}
|
|
>
|
|
Access Details
|
|
</button>
|
|
)}
|
|
<button
|
|
disabled={!isPublishable() || !hasChanges}
|
|
className={`${!isPublishable() || !hasChanges ? 'cursor-not-allowed opacity-30' : ''} bg-purple-30 hover:bg-violets-are-blue flex w-28 items-center justify-center rounded-3xl py-2 text-sm font-medium text-white`}
|
|
onClick={handlePublish}
|
|
>
|
|
<span className="flex items-center justify-center transition-all duration-200">
|
|
{publishLoading ? (
|
|
<Spinner size="small" color="white" />
|
|
) : (
|
|
modeConfig[effectiveMode].buttonText
|
|
)}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="mt-5 flex w-full grid-cols-5 flex-col gap-10 min-[1180px]:grid min-[1180px]:gap-5">
|
|
<div className="col-span-2 flex flex-col gap-5">
|
|
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Meta</h2>
|
|
<input
|
|
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
|
type="text"
|
|
value={agent.name}
|
|
placeholder="Agent name"
|
|
onChange={(e) => setAgent({ ...agent, name: e.target.value })}
|
|
/>
|
|
<textarea
|
|
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 h-32 w-full rounded-3xl border bg-white px-5 py-4 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
|
placeholder="Describe your agent"
|
|
value={agent.description}
|
|
onChange={(e) =>
|
|
setAgent({ ...agent, description: e.target.value })
|
|
}
|
|
/>
|
|
<div className="mt-3">
|
|
<FileUpload
|
|
showPreview
|
|
className="dark:bg-raisin-black"
|
|
onUpload={handleUpload}
|
|
onRemove={() => setImageFile(null)}
|
|
uploadText={[
|
|
{ text: 'Click to upload', colorClass: 'text-[#7D54D1]' },
|
|
{
|
|
text: ' or drag and drop',
|
|
colorClass: 'text-[#525252]',
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Source</h2>
|
|
<div className="mt-3">
|
|
<div className="flex flex-wrap items-center gap-1">
|
|
<button
|
|
ref={sourceAnchorButtonRef}
|
|
onClick={() => setIsSourcePopupOpen(!isSourcePopupOpen)}
|
|
className={`border-silver dark:bg-raisin-black w-full truncate rounded-3xl border bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] ${
|
|
selectedSourceIds.size > 0
|
|
? 'text-jet dark:text-bright-gray'
|
|
: 'dark:text-silver text-gray-400'
|
|
}`}
|
|
>
|
|
{selectedSourceIds.size > 0
|
|
? Array.from(selectedSourceIds)
|
|
.map(
|
|
(id) =>
|
|
sourceDocs?.find(
|
|
(source) =>
|
|
source.id === id ||
|
|
source.name === id ||
|
|
source.retriever === id,
|
|
)?.name,
|
|
)
|
|
.filter(Boolean)
|
|
.join(', ')
|
|
: 'Select source'}
|
|
</button>
|
|
<MultiSelectPopup
|
|
isOpen={isSourcePopupOpen}
|
|
onClose={() => setIsSourcePopupOpen(false)}
|
|
anchorRef={sourceAnchorButtonRef}
|
|
options={
|
|
sourceDocs?.map((doc: Doc) => ({
|
|
id: doc.id || doc.retriever || doc.name,
|
|
label: doc.name,
|
|
icon: <img src={SourceIcon} alt="" />,
|
|
})) || []
|
|
}
|
|
selectedIds={selectedSourceIds}
|
|
onSelectionChange={(newSelectedIds: Set<string | number>) => {
|
|
setSelectedSourceIds(newSelectedIds);
|
|
setIsSourcePopupOpen(false);
|
|
}}
|
|
title="Select Source"
|
|
searchPlaceholder="Search sources..."
|
|
noOptionsMessage="No source available"
|
|
singleSelect={true}
|
|
/>
|
|
</div>
|
|
<div className="mt-3">
|
|
<Dropdown
|
|
options={chunks}
|
|
selectedValue={agent.chunks ? agent.chunks : null}
|
|
onSelect={(value: string) =>
|
|
setAgent({ ...agent, chunks: value })
|
|
}
|
|
size="w-full"
|
|
rounded="3xl"
|
|
border="border"
|
|
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
|
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
|
placeholder="Chunks per query"
|
|
placeholderClassName="text-gray-400 dark:text-silver"
|
|
contentSize="text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Prompt</h2>
|
|
<div className="mt-3 flex flex-wrap items-center gap-1">
|
|
<div className="min-w-20 grow basis-full sm:basis-0">
|
|
<Dropdown
|
|
options={prompts.map((prompt) => ({
|
|
label: prompt.name,
|
|
value: prompt.id,
|
|
}))}
|
|
selectedValue={
|
|
agent.prompt_id
|
|
? prompts.filter(
|
|
(prompt) => prompt.id === agent.prompt_id,
|
|
)[0]?.name || null
|
|
: null
|
|
}
|
|
onSelect={(option: { label: string; value: string }) =>
|
|
setAgent({ ...agent, prompt_id: option.value })
|
|
}
|
|
size="w-full"
|
|
rounded="3xl"
|
|
border="border"
|
|
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
|
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E] dark:border-[#7E7E7E] dark:bg-dark-charcoal"
|
|
placeholderClassName="text-gray-400 dark:text-silver"
|
|
placeholder="Select a prompt"
|
|
contentSize="text-sm"
|
|
/>
|
|
</div>
|
|
<button
|
|
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue w-20 shrink-0 basis-full rounded-3xl border-2 border-solid px-5 py-[11px] text-sm transition-colors hover:text-white sm:basis-auto"
|
|
onClick={() => setAddPromptModal('ACTIVE')}
|
|
>
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Tools</h2>
|
|
<div className="mt-3 flex flex-wrap items-center gap-1">
|
|
<button
|
|
ref={toolAnchorButtonRef}
|
|
onClick={() => setIsToolsPopupOpen(!isToolsPopupOpen)}
|
|
className={`border-silver dark:bg-raisin-black w-full truncate rounded-3xl border bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] ${
|
|
selectedToolIds.size > 0
|
|
? 'text-jet dark:text-bright-gray'
|
|
: 'dark:text-silver text-gray-400'
|
|
}`}
|
|
>
|
|
{selectedToolIds.size > 0
|
|
? Array.from(selectedToolIds)
|
|
.map(
|
|
(id) => userTools.find((tool) => tool.id === id)?.label,
|
|
)
|
|
.filter(Boolean)
|
|
.join(', ')
|
|
: 'Select tools'}
|
|
</button>
|
|
<MultiSelectPopup
|
|
isOpen={isToolsPopupOpen}
|
|
onClose={() => setIsToolsPopupOpen(false)}
|
|
anchorRef={toolAnchorButtonRef}
|
|
options={userTools}
|
|
selectedIds={selectedToolIds}
|
|
onSelectionChange={(newSelectedIds: Set<string | number>) =>
|
|
setSelectedToolIds(newSelectedIds)
|
|
}
|
|
title="Select Tools"
|
|
searchPlaceholder="Search tools..."
|
|
noOptionsMessage="No tools available"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Agent type</h2>
|
|
<div className="mt-3">
|
|
<Dropdown
|
|
options={agentTypes}
|
|
selectedValue={
|
|
agent.agent_type
|
|
? agentTypes.find((type) => type.value === agent.agent_type)
|
|
?.label || null
|
|
: null
|
|
}
|
|
onSelect={(option: { label: string; value: string }) =>
|
|
setAgent({ ...agent, agent_type: option.value })
|
|
}
|
|
size="w-full"
|
|
rounded="3xl"
|
|
border="border"
|
|
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
|
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
|
placeholder="Select type"
|
|
placeholderClassName="text-gray-400 dark:text-silver"
|
|
contentSize="text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-span-3 flex flex-col gap-3 rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
|
<h2 className="text-lg font-semibold">Preview</h2>
|
|
<AgentPreviewArea />
|
|
</div>
|
|
</div>
|
|
<ConfirmationModal
|
|
message="Are you sure you want to delete this agent?"
|
|
modalState={deleteConfirmation}
|
|
setModalState={setDeleteConfirmation}
|
|
submitLabel="Delete"
|
|
handleSubmit={() => {
|
|
handleDelete(agent.id || '');
|
|
setDeleteConfirmation('INACTIVE');
|
|
}}
|
|
cancelLabel="Cancel"
|
|
variant="danger"
|
|
/>
|
|
<AgentDetailsModal
|
|
agent={agent}
|
|
mode={effectiveMode}
|
|
modalState={agentDetails}
|
|
setModalState={setAgentDetails}
|
|
/>
|
|
<AddPromptModal
|
|
prompts={prompts}
|
|
setPrompts={setPrompts}
|
|
isOpen={addPromptModal}
|
|
onClose={() => setAddPromptModal('INACTIVE')}
|
|
onSelect={(name: string, id: string, type: string) => {
|
|
setAgent({ ...agent, prompt_id: id });
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AgentPreviewArea() {
|
|
const selectedAgent = useSelector(selectSelectedAgent);
|
|
return (
|
|
<div className="dark:bg-raisin-black h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white max-[1180px]:h-192 dark:border-[#7E7E7E]">
|
|
{selectedAgent?.status === 'published' ? (
|
|
<div className="flex h-full w-full flex-col justify-end overflow-auto rounded-[30px]">
|
|
<AgentPreview />
|
|
</div>
|
|
) : (
|
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
|
|
<span className="block h-12 w-12 bg-[url('/src/assets/science-spark.svg')] bg-contain bg-center bg-no-repeat transition-all dark:bg-[url('/src/assets/science-spark-dark.svg')]" />{' '}
|
|
<p className="dark:text-gray-4000 text-xs text-[#18181B]">
|
|
Published agents can be previewed here
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AddPromptModal({
|
|
prompts,
|
|
setPrompts,
|
|
isOpen,
|
|
onClose,
|
|
onSelect,
|
|
}: {
|
|
prompts: Prompt[];
|
|
setPrompts?: React.Dispatch<React.SetStateAction<Prompt[]>>;
|
|
isOpen: ActiveState;
|
|
onClose: () => void;
|
|
onSelect?: (name: string, id: string, type: string) => void;
|
|
}) {
|
|
const token = useSelector(selectToken);
|
|
|
|
const [newPromptName, setNewPromptName] = useState('');
|
|
const [newPromptContent, setNewPromptContent] = useState('');
|
|
|
|
const handleAddPrompt = async () => {
|
|
try {
|
|
const response = await userService.createPrompt(
|
|
{
|
|
name: newPromptName,
|
|
content: newPromptContent,
|
|
},
|
|
token,
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to add prompt');
|
|
}
|
|
const newPrompt = await response.json();
|
|
if (setPrompts) {
|
|
setPrompts([
|
|
...prompts,
|
|
{ name: newPromptName, id: newPrompt.id, type: 'private' },
|
|
]);
|
|
}
|
|
onClose();
|
|
setNewPromptName('');
|
|
setNewPromptContent('');
|
|
onSelect?.(newPromptName, newPrompt.id, newPromptContent);
|
|
} catch (error) {
|
|
console.error('Error adding prompt:', error);
|
|
}
|
|
};
|
|
return (
|
|
<PromptsModal
|
|
modalState={isOpen}
|
|
setModalState={onClose}
|
|
type="ADD"
|
|
existingPrompts={prompts}
|
|
newPromptName={newPromptName}
|
|
setNewPromptName={setNewPromptName}
|
|
newPromptContent={newPromptContent}
|
|
setNewPromptContent={setNewPromptContent}
|
|
editPromptName={''}
|
|
setEditPromptName={() => undefined}
|
|
editPromptContent={''}
|
|
setEditPromptContent={() => undefined}
|
|
currentPromptEdit={{ id: '', name: '', type: '' }}
|
|
handleAddPrompt={handleAddPrompt}
|
|
/>
|
|
);
|
|
}
|