mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 20:00:15 +01:00
🤖 feat: Add Agent Duplication Functionality with Permission (#5022)
* 🤖 feat: Add Agent Duplication Functionality with Permission * 🐛 fix: Enhance Agent Duplication Logic and Filter Sensitive Data * refactor(agents/v1): reorganized variables and error logging * refactor: remove duplication permission * chore: update librechat-data-provider version to 0.7.64 * fix: optimize agent duplication --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
This commit is contained in:
parent
16eed5f32d
commit
18ad89be2c
17 changed files with 270 additions and 38 deletions
|
|
@ -142,7 +142,7 @@ const AdminSettings = () => {
|
|||
<Button
|
||||
size={'sm'}
|
||||
variant={'outline'}
|
||||
className="btn btn-neutral border-token-border-light relative my-1 h-9 w-full gap-1 rounded-lg font-medium"
|
||||
className="btn btn-neutral border-token-border-light relative mb-4 h-9 w-full gap-1 rounded-lg font-medium"
|
||||
>
|
||||
<ShieldEllipsis className="cursor-pointer" />
|
||||
{localize('com_ui_admin_settings')}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@ import { useToastContext, useFileMapContext } from '~/Providers';
|
|||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||
import Action from '~/components/SidePanel/Builder/Action';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
import DuplicateAgent from './DuplicateAgent';
|
||||
import { processAgentOption } from '~/utils';
|
||||
import AdminSettings from './AdminSettings';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import AgentAvatar from './AgentAvatar';
|
||||
import { Spinner } from '~/components';
|
||||
import FileSearch from './FileSearch';
|
||||
import ShareAgent from './ShareAgent';
|
||||
import AgentTool from './AgentTool';
|
||||
|
|
@ -421,6 +422,7 @@ export default function AgentConfig({
|
|||
isCollaborative={agent?.isCollaborative}
|
||||
/>
|
||||
)}
|
||||
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
className="btn btn-primary focus:shadow-outline flex h-9 w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { createProviderOption } from '~/utils';
|
|||
import { useToastContext } from '~/Providers';
|
||||
import AgentConfig from './AgentConfig';
|
||||
import AgentSelect from './AgentSelect';
|
||||
import { Button } from '~/components';
|
||||
import ModelPanel from './ModelPanel';
|
||||
import { Panel } from '~/common';
|
||||
|
||||
|
|
@ -208,7 +209,7 @@ export default function AgentPanel({
|
|||
className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden"
|
||||
aria-label="Agent configuration form"
|
||||
>
|
||||
<div className="flex w-full flex-wrap">
|
||||
<div className="mt-2 flex w-full flex-wrap gap-2">
|
||||
<Controller
|
||||
name="agent"
|
||||
control={control}
|
||||
|
|
@ -225,15 +226,17 @@ export default function AgentPanel({
|
|||
/>
|
||||
{/* Select Button */}
|
||||
{agent_id && (
|
||||
<button
|
||||
className="btn btn-primary focus:shadow-outline mx-2 mt-1 h-[40px] rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0"
|
||||
type="button"
|
||||
<Button
|
||||
variant="submit"
|
||||
disabled={!agent_id}
|
||||
onClick={handleSelectAgent}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelectAgent();
|
||||
}}
|
||||
aria-label="Select agent"
|
||||
>
|
||||
{localize('com_ui_select')}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{!canEditAgent && (
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ export default function AgentPanelSkeleton() {
|
|||
<div className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden">
|
||||
{/* Agent Select and Button */}
|
||||
<div className="mt-1 flex w-full gap-2">
|
||||
<Skeleton className="h-[40px] w-3/4 rounded" />
|
||||
<Skeleton className="h-[40px] w-1/4 rounded" />
|
||||
<Skeleton className="h-[40px] w-4/5 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-1/5 rounded-lg" />
|
||||
</div>
|
||||
|
||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
|
|
@ -17,52 +17,60 @@ export default function AgentPanelSkeleton() {
|
|||
<Skeleton className="relative h-20 w-20 rounded-full" />
|
||||
</div>
|
||||
{/* Name */}
|
||||
<Skeleton className="mb-2 h-5 w-1/5 rounded" />
|
||||
<Skeleton className="mb-1 h-[40px] w-full rounded" />
|
||||
<Skeleton className="h-3 w-1/4 rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
|
||||
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
|
||||
<Skeleton className="h-3 w-1/4 rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-4">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
||||
<Skeleton className="h-[40px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
||||
<Skeleton className="h-[100px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[100px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Model and Provider */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
||||
<Skeleton className="h-[40px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Capabilities */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
||||
<Skeleton className="h-[40px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
|
||||
<Skeleton className="mb-4 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-5 w-24 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Tools & Actions */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
||||
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||
<div className="flex space-x-2">
|
||||
<Skeleton className="h-8 w-1/2 rounded" />
|
||||
<Skeleton className="h-8 w-1/2 rounded" />
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Admin Settings */}
|
||||
<div className="mb-6">
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
|
||||
{/* Bottom Buttons */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -185,8 +185,8 @@ export default function AgentSelect({
|
|||
hasAgentValue ? 'text-gray-500' : '',
|
||||
)}
|
||||
className={cn(
|
||||
'mt-1 rounded-md dark:border-gray-700 dark:bg-gray-850',
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center px-4 hover:cursor-pointer hover:border-green-500 focus:border-gray-400',
|
||||
'rounded-md dark:border-gray-700 dark:bg-gray-850',
|
||||
'z-50 flex h-[40px] w-full flex-none items-center justify-center truncate px-4 hover:cursor-pointer hover:border-green-500 focus:border-gray-400',
|
||||
)}
|
||||
renderOption={() => (
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
|
|
|
|||
50
client/src/components/SidePanel/Agents/DuplicateAgent.tsx
Normal file
50
client/src/components/SidePanel/Agents/DuplicateAgent.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { CopyIcon } from 'lucide-react';
|
||||
import { useDuplicateAgentMutation } from '~/data-provider';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function DuplicateAgent({ agent_id }: { agent_id: string }) {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
||||
const duplicateAgent = useDuplicateAgentMutation({
|
||||
onSuccess: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_agent_duplicated'),
|
||||
status: 'success',
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
showToast({
|
||||
message: localize('com_ui_agent_duplicate_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!agent_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleDuplicate = () => {
|
||||
duplicateAgent.mutate({ agent_id });
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
'btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
aria-label={localize('com_ui_duplicate') + ' ' + localize('com_ui_agent')}
|
||||
type="button"
|
||||
onClick={handleDuplicate}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2 text-primary">
|
||||
<CopyIcon className="size-4" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue