mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02: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
|
@ -20,7 +20,7 @@ const Agent = mongoose.model('agent', agentSchema);
|
||||||
* @throws {Error} If the agent creation fails.
|
* @throws {Error} If the agent creation fails.
|
||||||
*/
|
*/
|
||||||
const createAgent = async (agentData) => {
|
const createAgent = async (agentData) => {
|
||||||
return await Agent.create(agentData);
|
return (await Agent.create(agentData)).toObject();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { FileContext, Constants, Tools, SystemRoles } = require('librechat-data-provider');
|
const {
|
||||||
|
FileContext,
|
||||||
|
Constants,
|
||||||
|
Tools,
|
||||||
|
SystemRoles,
|
||||||
|
actionDelimiter,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getAgent,
|
getAgent,
|
||||||
createAgent,
|
createAgent,
|
||||||
|
@ -10,6 +16,7 @@ const {
|
||||||
} = require('~/models/Agent');
|
} = require('~/models/Agent');
|
||||||
const { uploadImageBuffer, filterFile } = require('~/server/services/Files/process');
|
const { uploadImageBuffer, filterFile } = require('~/server/services/Files/process');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
|
const { updateAction, getActions } = require('~/models/Action');
|
||||||
const { getProjectByName } = require('~/models/Project');
|
const { getProjectByName } = require('~/models/Project');
|
||||||
const { updateAgentProjects } = require('~/models/Agent');
|
const { updateAgentProjects } = require('~/models/Agent');
|
||||||
const { deleteFileByFilter } = require('~/models/File');
|
const { deleteFileByFilter } = require('~/models/File');
|
||||||
|
@ -173,6 +180,99 @@ const updateAgentHandler = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates an Agent based on the provided ID.
|
||||||
|
* @route POST /Agents/:id/duplicate
|
||||||
|
* @param {object} req - Express Request
|
||||||
|
* @param {object} req.params - Request params
|
||||||
|
* @param {string} req.params.id - Agent identifier.
|
||||||
|
* @returns {Agent} 201 - success response - application/json
|
||||||
|
*/
|
||||||
|
const duplicateAgentHandler = async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { id: userId } = req.user;
|
||||||
|
const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const agent = await getAgent({ id });
|
||||||
|
if (!agent) {
|
||||||
|
return res.status(404).json({
|
||||||
|
error: 'Agent not found',
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
_id: __id,
|
||||||
|
id: _id,
|
||||||
|
author: _author,
|
||||||
|
createdAt: _createdAt,
|
||||||
|
updatedAt: _updatedAt,
|
||||||
|
...cloneData
|
||||||
|
} = agent;
|
||||||
|
|
||||||
|
const newAgentId = `agent_${nanoid()}`;
|
||||||
|
const newAgentData = Object.assign(cloneData, {
|
||||||
|
id: newAgentId,
|
||||||
|
author: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newActionsList = [];
|
||||||
|
const originalActions = (await getActions({ agent_id: id }, true)) ?? [];
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates an action and returns the new action ID.
|
||||||
|
* @param {Action} action
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
const duplicateAction = async (action) => {
|
||||||
|
const newActionId = nanoid();
|
||||||
|
const [domain] = action.action_id.split(actionDelimiter);
|
||||||
|
const fullActionId = `${domain}${actionDelimiter}${newActionId}`;
|
||||||
|
|
||||||
|
const newAction = await updateAction(
|
||||||
|
{ action_id: newActionId },
|
||||||
|
{
|
||||||
|
metadata: action.metadata,
|
||||||
|
agent_id: newAgentId,
|
||||||
|
user: userId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredMetadata = { ...newAction.metadata };
|
||||||
|
for (const field of sensitiveFields) {
|
||||||
|
delete filteredMetadata[field];
|
||||||
|
}
|
||||||
|
|
||||||
|
newAction.metadata = filteredMetadata;
|
||||||
|
newActionsList.push(newAction);
|
||||||
|
return fullActionId;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const action of originalActions) {
|
||||||
|
promises.push(
|
||||||
|
duplicateAction(action).catch((error) => {
|
||||||
|
logger.error('[/agents/:id/duplicate] Error duplicating Action:', error);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentActions = await Promise.all(promises);
|
||||||
|
newAgentData.actions = agentActions;
|
||||||
|
const newAgent = await createAgent(newAgentData);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
agent: newAgent,
|
||||||
|
actions: newActionsList,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[/Agents/:id/duplicate] Error duplicating Agent:', error);
|
||||||
|
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an Agent based on the provided ID.
|
* Deletes an Agent based on the provided ID.
|
||||||
* @route DELETE /Agents/:id
|
* @route DELETE /Agents/:id
|
||||||
|
@ -292,6 +392,7 @@ module.exports = {
|
||||||
createAgent: createAgentHandler,
|
createAgent: createAgentHandler,
|
||||||
getAgent: getAgentHandler,
|
getAgent: getAgentHandler,
|
||||||
updateAgent: updateAgentHandler,
|
updateAgent: updateAgentHandler,
|
||||||
|
duplicateAgent: duplicateAgentHandler,
|
||||||
deleteAgent: deleteAgentHandler,
|
deleteAgent: deleteAgentHandler,
|
||||||
getListAgents: getListAgentsHandler,
|
getListAgents: getListAgentsHandler,
|
||||||
uploadAgentAvatar: uploadAgentAvatarHandler,
|
uploadAgentAvatar: uploadAgentAvatarHandler,
|
||||||
|
|
|
@ -62,6 +62,14 @@ router.get('/:id', checkAgentAccess, v1.getAgent);
|
||||||
*/
|
*/
|
||||||
router.patch('/:id', checkGlobalAgentShare, v1.updateAgent);
|
router.patch('/:id', checkGlobalAgentShare, v1.updateAgent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates an agent.
|
||||||
|
* @route POST /agents/:id/duplicate
|
||||||
|
* @param {string} req.params.id - Agent identifier.
|
||||||
|
* @returns {Agent} 201 - Success response - application/json
|
||||||
|
*/
|
||||||
|
router.post('/:id/duplicate', checkAgentCreate, v1.duplicateAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an agent.
|
* Deletes an agent.
|
||||||
* @route DELETE /agents/:id
|
* @route DELETE /agents/:id
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useLocalize, useLocalStorage } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { TooltipAnchor } from '~/components/ui';
|
import { TooltipAnchor } from '~/components/ui';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ const AdminSettings = () => {
|
||||||
<Button
|
<Button
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'outline'}
|
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" />
|
<ShieldEllipsis className="cursor-pointer" />
|
||||||
{localize('com_ui_admin_settings')}
|
{localize('com_ui_admin_settings')}
|
||||||
|
|
|
@ -18,11 +18,12 @@ import { useToastContext, useFileMapContext } from '~/Providers';
|
||||||
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
|
||||||
import Action from '~/components/SidePanel/Builder/Action';
|
import Action from '~/components/SidePanel/Builder/Action';
|
||||||
import { ToolSelectDialog } from '~/components/Tools';
|
import { ToolSelectDialog } from '~/components/Tools';
|
||||||
|
import DuplicateAgent from './DuplicateAgent';
|
||||||
import { processAgentOption } from '~/utils';
|
import { processAgentOption } from '~/utils';
|
||||||
import AdminSettings from './AdminSettings';
|
import AdminSettings from './AdminSettings';
|
||||||
import { Spinner } from '~/components/svg';
|
|
||||||
import DeleteButton from './DeleteButton';
|
import DeleteButton from './DeleteButton';
|
||||||
import AgentAvatar from './AgentAvatar';
|
import AgentAvatar from './AgentAvatar';
|
||||||
|
import { Spinner } from '~/components';
|
||||||
import FileSearch from './FileSearch';
|
import FileSearch from './FileSearch';
|
||||||
import ShareAgent from './ShareAgent';
|
import ShareAgent from './ShareAgent';
|
||||||
import AgentTool from './AgentTool';
|
import AgentTool from './AgentTool';
|
||||||
|
@ -421,6 +422,7 @@ export default function AgentConfig({
|
||||||
isCollaborative={agent?.isCollaborative}
|
isCollaborative={agent?.isCollaborative}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{agent && agent.author === user?.id && <DuplicateAgent agent_id={agent_id} />}
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<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"
|
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 { useToastContext } from '~/Providers';
|
||||||
import AgentConfig from './AgentConfig';
|
import AgentConfig from './AgentConfig';
|
||||||
import AgentSelect from './AgentSelect';
|
import AgentSelect from './AgentSelect';
|
||||||
|
import { Button } from '~/components';
|
||||||
import ModelPanel from './ModelPanel';
|
import ModelPanel from './ModelPanel';
|
||||||
import { Panel } from '~/common';
|
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"
|
className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden"
|
||||||
aria-label="Agent configuration form"
|
aria-label="Agent configuration form"
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-wrap">
|
<div className="mt-2 flex w-full flex-wrap gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="agent"
|
name="agent"
|
||||||
control={control}
|
control={control}
|
||||||
|
@ -225,15 +226,17 @@ export default function AgentPanel({
|
||||||
/>
|
/>
|
||||||
{/* Select Button */}
|
{/* Select Button */}
|
||||||
{agent_id && (
|
{agent_id && (
|
||||||
<button
|
<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"
|
variant="submit"
|
||||||
type="button"
|
|
||||||
disabled={!agent_id}
|
disabled={!agent_id}
|
||||||
onClick={handleSelectAgent}
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSelectAgent();
|
||||||
|
}}
|
||||||
aria-label="Select agent"
|
aria-label="Select agent"
|
||||||
>
|
>
|
||||||
{localize('com_ui_select')}
|
{localize('com_ui_select')}
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!canEditAgent && (
|
{!canEditAgent && (
|
||||||
|
|
|
@ -6,8 +6,8 @@ export default function AgentPanelSkeleton() {
|
||||||
<div className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden">
|
<div className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 overflow-x-hidden">
|
||||||
{/* Agent Select and Button */}
|
{/* Agent Select and Button */}
|
||||||
<div className="mt-1 flex w-full gap-2">
|
<div className="mt-1 flex w-full gap-2">
|
||||||
<Skeleton className="h-[40px] w-3/4 rounded" />
|
<Skeleton className="h-[40px] w-4/5 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-1/4 rounded" />
|
<Skeleton className="h-[40px] w-1/5 rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
<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" />
|
<Skeleton className="relative h-20 w-20 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<Skeleton className="mb-2 h-5 w-1/5 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/5 rounded-lg" />
|
||||||
<Skeleton className="mb-1 h-[40px] w-full rounded" />
|
<Skeleton className="mb-1 h-[40px] w-full rounded-lg" />
|
||||||
<Skeleton className="h-3 w-1/4 rounded" />
|
<Skeleton className="h-3 w-1/4 rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-full rounded" />
|
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||||
<Skeleton className="h-[100px] w-full rounded" />
|
<Skeleton className="h-[100px] w-full rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Model and Provider */}
|
{/* Model and Provider */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-full rounded" />
|
<Skeleton className="h-[40px] w-full rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Capabilities */}
|
{/* Capabilities */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
<Skeleton className="mb-2 h-5 w-36 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-full rounded" />
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Tools & Actions */}
|
{/* Tools & Actions */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Skeleton className="mb-2 h-5 w-1/4 rounded" />
|
<Skeleton className="mb-2 h-5 w-1/4 rounded-lg" />
|
||||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||||
<Skeleton className="mb-2 h-[40px] w-full rounded" />
|
<Skeleton className="mb-2 h-[35px] w-full rounded-lg" />
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<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" />
|
<Skeleton className="h-8 w-1/2 rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Admin Settings */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Bottom Buttons */}
|
{/* Bottom Buttons */}
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||||
<Skeleton className="h-[40px] w-[100px] rounded" />
|
<Skeleton className="h-[35px] w-16 rounded-lg" />
|
||||||
|
<Skeleton className="h-[35px] w-full rounded-lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -185,8 +185,8 @@ export default function AgentSelect({
|
||||||
hasAgentValue ? 'text-gray-500' : '',
|
hasAgentValue ? 'text-gray-500' : '',
|
||||||
)}
|
)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 rounded-md dark:border-gray-700 dark:bg-gray-850',
|
'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',
|
'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={() => (
|
renderOption={() => (
|
||||||
<span className="flex items-center gap-1.5 truncate">
|
<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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
|
@ -121,6 +121,43 @@ export const useDeleteAgentMutation = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for duplicating an agent
|
||||||
|
*/
|
||||||
|
export const useDuplicateAgentMutation = (
|
||||||
|
options?: t.DuplicateAgentMutationOptions,
|
||||||
|
): UseMutationResult<{ agent: t.Agent; actions: t.Action[] }, Error, t.DuplicateAgentBody> => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{ agent: t.Agent; actions: t.Action[] }, Error, t.DuplicateAgentBody>(
|
||||||
|
(params: t.DuplicateAgentBody) => dataService.duplicateAgent(params),
|
||||||
|
{
|
||||||
|
onMutate: options?.onMutate,
|
||||||
|
onError: options?.onError,
|
||||||
|
onSuccess: ({ agent, actions }, variables, context) => {
|
||||||
|
const listRes = queryClient.getQueryData<t.AgentListResponse>([
|
||||||
|
QueryKeys.agents,
|
||||||
|
defaultOrderQuery,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (listRes) {
|
||||||
|
const currentAgents = [agent, ...listRes.data];
|
||||||
|
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], {
|
||||||
|
...listRes,
|
||||||
|
data: currentAgents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingActions = queryClient.getQueryData<t.Action[]>([QueryKeys.actions]) || [];
|
||||||
|
|
||||||
|
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], existingActions.concat(actions));
|
||||||
|
|
||||||
|
return options?.onSuccess?.({ agent, actions }, variables, context);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook for uploading an agent avatar
|
* Hook for uploading an agent avatar
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -361,6 +361,8 @@ export default {
|
||||||
com_ui_agents_allow_share_global: 'Allow sharing Agents to all users',
|
com_ui_agents_allow_share_global: 'Allow sharing Agents to all users',
|
||||||
com_ui_agents_allow_use: 'Allow using Agents',
|
com_ui_agents_allow_use: 'Allow using Agents',
|
||||||
com_ui_agents_allow_create: 'Allow creating Agents',
|
com_ui_agents_allow_create: 'Allow creating Agents',
|
||||||
|
com_ui_agent_duplicated: 'Agent duplicated successfully',
|
||||||
|
com_ui_agent_duplicate_error: 'There was an error duplicating the agent',
|
||||||
com_ui_prompt_already_shared_to_all: 'This prompt is already shared to all users',
|
com_ui_prompt_already_shared_to_all: 'This prompt is already shared to all users',
|
||||||
com_ui_description_placeholder: 'Optional: Enter a description to display for the prompt',
|
com_ui_description_placeholder: 'Optional: Enter a description to display for the prompt',
|
||||||
com_ui_command_placeholder: 'Optional: Enter a command for the prompt or name will be used.',
|
com_ui_command_placeholder: 'Optional: Enter a command for the prompt or name will be used.',
|
||||||
|
@ -428,6 +430,8 @@ export default {
|
||||||
com_ui_no_bookmarks: 'it seems like you have no bookmarks yet. Click on a chat and add a new one',
|
com_ui_no_bookmarks: 'it seems like you have no bookmarks yet. Click on a chat and add a new one',
|
||||||
com_ui_no_conversation_id: 'No conversation ID found',
|
com_ui_no_conversation_id: 'No conversation ID found',
|
||||||
com_ui_add_multi_conversation: 'Add multi-conversation',
|
com_ui_add_multi_conversation: 'Add multi-conversation',
|
||||||
|
com_ui_duplicate: 'Duplicate',
|
||||||
|
com_ui_duplicate_agent_confirm: 'Are you sure you want to duplicate this agent?',
|
||||||
com_auth_error_login:
|
com_auth_error_login:
|
||||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||||
com_auth_error_login_rl:
|
com_auth_error_login_rl:
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -36498,7 +36498,7 @@
|
||||||
},
|
},
|
||||||
"packages/data-provider": {
|
"packages/data-provider": {
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.63",
|
"version": "0.7.64",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.63",
|
"version": "0.7.64",
|
||||||
"description": "data services for librechat apps",
|
"description": "data services for librechat apps",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
|
@ -428,6 +428,16 @@ export const updateAgent = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const duplicateAgent = ({
|
||||||
|
agent_id,
|
||||||
|
}: m.DuplicateAgentBody): Promise<{ agent: a.Agent; actions: a.Action[] }> => {
|
||||||
|
return request.post(
|
||||||
|
endpoints.agents({
|
||||||
|
path: `${agent_id}/duplicate`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteAgent = ({ agent_id }: m.DeleteAgentBody): Promise<void> => {
|
export const deleteAgent = ({ agent_id }: m.DeleteAgentBody): Promise<void> => {
|
||||||
return request.delete(
|
return request.delete(
|
||||||
endpoints.agents({
|
endpoints.agents({
|
||||||
|
|
|
@ -126,6 +126,15 @@ export type UpdateAgentVariables = {
|
||||||
|
|
||||||
export type UpdateAgentMutationOptions = MutationOptions<Agent, UpdateAgentVariables>;
|
export type UpdateAgentMutationOptions = MutationOptions<Agent, UpdateAgentVariables>;
|
||||||
|
|
||||||
|
export type DuplicateAgentBody = {
|
||||||
|
agent_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DuplicateAgentMutationOptions = MutationOptions<
|
||||||
|
{ agent: Agent; actions: Action[] },
|
||||||
|
Pick<DuplicateAgentBody, 'agent_id'>
|
||||||
|
>;
|
||||||
|
|
||||||
export type DeleteAgentBody = {
|
export type DeleteAgentBody = {
|
||||||
agent_id: string;
|
agent_id: string;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue