mirror of https://github.com/kortix-ai/suna.git
Merge pull request #1467 from escapade-mckv/fix-integrations-saving
Fix integrations saving
This commit is contained in:
commit
14edb083e7
|
@ -120,6 +120,7 @@ class AgentUpdateRequest(BaseModel):
|
|||
icon_name: Optional[str] = None
|
||||
icon_color: Optional[str] = None
|
||||
icon_background: Optional[str] = None
|
||||
replace_mcps: Optional[bool] = None
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
agent_id: str
|
||||
|
@ -145,7 +146,7 @@ class AgentResponse(BaseModel):
|
|||
current_version: Optional[AgentVersionResponse] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
from utils.pagination import PaginationService, PaginationParams, PaginatedResponse
|
||||
from utils.pagination import PaginationParams
|
||||
|
||||
class PaginationInfo(BaseModel):
|
||||
current_page: int
|
||||
|
@ -2105,16 +2106,32 @@ async def update_agent(
|
|||
print(f"[DEBUG] update_agent: Prepared update_data with icon fields - icon_name={update_data.get('icon_name')}, icon_color={update_data.get('icon_color')}, icon_background={update_data.get('icon_background')}")
|
||||
|
||||
current_system_prompt = agent_data.system_prompt if agent_data.system_prompt is not None else current_version_data.get('system_prompt', '')
|
||||
current_configured_mcps = agent_data.configured_mcps if agent_data.configured_mcps is not None else current_version_data.get('configured_mcps', [])
|
||||
|
||||
if agent_data.configured_mcps is not None:
|
||||
if agent_data.replace_mcps:
|
||||
current_configured_mcps = agent_data.configured_mcps
|
||||
logger.debug(f"Replacing configured MCPs for agent {agent_id}: {current_configured_mcps}")
|
||||
else:
|
||||
current_configured_mcps = agent_data.configured_mcps
|
||||
else:
|
||||
current_configured_mcps = current_version_data.get('configured_mcps', [])
|
||||
|
||||
# Handle custom MCPs - either replace or merge based on the flag
|
||||
if agent_data.custom_mcps is not None:
|
||||
current_custom_mcps = merge_custom_mcps(
|
||||
current_version_data.get('custom_mcps', []),
|
||||
agent_data.custom_mcps
|
||||
)
|
||||
if agent_data.replace_mcps:
|
||||
# Replace mode: use the provided list as-is
|
||||
current_custom_mcps = agent_data.custom_mcps
|
||||
logger.debug(f"Replacing custom MCPs for agent {agent_id}: {current_custom_mcps}")
|
||||
else:
|
||||
# Merge mode: merge with existing MCPs (default behavior)
|
||||
current_custom_mcps = merge_custom_mcps(
|
||||
current_version_data.get('custom_mcps', []),
|
||||
agent_data.custom_mcps
|
||||
)
|
||||
logger.debug(f"Merging custom MCPs for agent {agent_id}")
|
||||
else:
|
||||
current_custom_mcps = current_version_data.get('custom_mcps', [])
|
||||
|
||||
|
||||
current_agentpress_tools = agent_data.agentpress_tools if agent_data.agentpress_tools is not None else current_version_data.get('agentpress_tools', {})
|
||||
current_avatar = agent_data.avatar if agent_data.avatar is not None else existing_data.get('avatar')
|
||||
current_avatar_color = agent_data.avatar_color if agent_data.avatar_color is not None else existing_data.get('avatar_color')
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
|
|||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer';
|
||||
import { useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
|
||||
import { useCreateAgentVersion, useActivateAgentVersion } from '@/hooks/react-query/agents/use-agent-versions';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
|
@ -23,7 +24,6 @@ import { useAgentConfigTour } from '@/hooks/use-agent-config-tour';
|
|||
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
|
||||
import { TourConfirmationDialog } from '@/components/tour/TourConfirmationDialog';
|
||||
|
||||
// Tour steps for agent configuration
|
||||
const agentConfigTourSteps: Step[] = [
|
||||
{
|
||||
target: '[data-tour="agent-header"]',
|
||||
|
@ -90,7 +90,6 @@ const agentConfigTourSteps: Step[] = [
|
|||
},
|
||||
];
|
||||
|
||||
// Form data interface
|
||||
interface FormData {
|
||||
name: string;
|
||||
description: string;
|
||||
|
@ -119,10 +118,31 @@ function AgentConfigurationContent() {
|
|||
const { setHasUnsavedChanges } = useAgentVersionStore();
|
||||
|
||||
const updateAgentMutation = useUpdateAgent();
|
||||
const updateAgentMCPsMutation = useUpdateAgentMCPs();
|
||||
const createVersionMutation = useCreateAgentVersion();
|
||||
const activateVersionMutation = useActivateAgentVersion();
|
||||
const exportMutation = useExportAgent();
|
||||
|
||||
// Use refs for stable references to avoid callback recreation
|
||||
const agentIdRef = useRef(agentId);
|
||||
const mutationsRef = useRef({
|
||||
updateAgent: updateAgentMutation,
|
||||
updateMCPs: updateAgentMCPsMutation,
|
||||
export: exportMutation,
|
||||
activate: activateVersionMutation,
|
||||
});
|
||||
|
||||
// Update refs when values change
|
||||
useEffect(() => {
|
||||
agentIdRef.current = agentId;
|
||||
mutationsRef.current = {
|
||||
updateAgent: updateAgentMutation,
|
||||
updateMCPs: updateAgentMCPsMutation,
|
||||
export: exportMutation,
|
||||
activate: activateVersionMutation,
|
||||
};
|
||||
}, [agentId, updateAgentMutation, updateAgentMCPsMutation, exportMutation, activateVersionMutation]);
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: '',
|
||||
description: '',
|
||||
|
@ -140,9 +160,19 @@ function AgentConfigurationContent() {
|
|||
|
||||
const [originalData, setOriginalData] = useState<FormData>(formData);
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const [lastLoadedVersionId, setLastLoadedVersionId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent) return;
|
||||
|
||||
const currentVersionId = versionData?.version_id || agent.current_version_id || 'current';
|
||||
const shouldResetForm = !lastLoadedVersionId || lastLoadedVersionId !== currentVersionId;
|
||||
|
||||
if (!shouldResetForm) {
|
||||
setLastLoadedVersionId(currentVersionId);
|
||||
return;
|
||||
}
|
||||
|
||||
let configSource = agent;
|
||||
if (versionData) {
|
||||
configSource = {
|
||||
|
@ -174,7 +204,8 @@ function AgentConfigurationContent() {
|
|||
};
|
||||
setFormData(newFormData);
|
||||
setOriginalData(newFormData);
|
||||
}, [agent, versionData]);
|
||||
setLastLoadedVersionId(currentVersionId);
|
||||
}, [agent, versionData, lastLoadedVersionId]);
|
||||
|
||||
const displayData = isViewingOldVersion && versionData ? {
|
||||
name: formData.name,
|
||||
|
@ -191,24 +222,127 @@ function AgentConfigurationContent() {
|
|||
icon_background: versionData.icon_background || formData.icon_background || '#e5e5e5',
|
||||
} : formData;
|
||||
|
||||
const handleFieldChange = useCallback((field: string, value: any) => {
|
||||
const handleFieldChange = (field: string, value: any) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleMCPChange = useCallback((updates: { configured_mcps: any[]; custom_mcps: any[] }) => {
|
||||
const previousConfigured = formData.configured_mcps;
|
||||
const previousCustom = formData.custom_mcps;
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
configured_mcps: updates.configured_mcps,
|
||||
custom_mcps: updates.custom_mcps
|
||||
configured_mcps: updates.configured_mcps || [],
|
||||
custom_mcps: updates.custom_mcps || []
|
||||
}));
|
||||
|
||||
mutationsRef.current.updateMCPs.mutate({
|
||||
agentId: agentIdRef.current,
|
||||
configured_mcps: updates.configured_mcps || [],
|
||||
custom_mcps: updates.custom_mcps || [],
|
||||
replace_mcps: true
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
setOriginalData(prev => ({
|
||||
...prev,
|
||||
configured_mcps: updates.configured_mcps || [],
|
||||
custom_mcps: updates.custom_mcps || []
|
||||
}));
|
||||
toast.success('MCP configuration updated');
|
||||
},
|
||||
onError: (error) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
configured_mcps: previousConfigured,
|
||||
custom_mcps: previousCustom
|
||||
}));
|
||||
toast.error('Failed to update MCP configuration');
|
||||
console.error('MCP update error:', error);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
exportMutation.mutate(agentId);
|
||||
}, [agentId, exportMutation]);
|
||||
const saveField = useCallback(async (fieldData: Partial<FormData>) => {
|
||||
try {
|
||||
await mutationsRef.current.updateAgent.mutateAsync({
|
||||
agentId: agentIdRef.current,
|
||||
...fieldData,
|
||||
});
|
||||
|
||||
setFormData(prev => ({ ...prev, ...fieldData }));
|
||||
setOriginalData(prev => ({ ...prev, ...fieldData }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save field:', error);
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleNameSave = async (name: string) => {
|
||||
try {
|
||||
await saveField({ name });
|
||||
toast.success('Agent name updated');
|
||||
} catch {
|
||||
toast.error('Failed to update agent name');
|
||||
throw new Error('Failed to update agent name');
|
||||
}
|
||||
};
|
||||
|
||||
const handleProfileImageSave = async (profileImageUrl: string | null) => {
|
||||
try {
|
||||
await saveField({ profile_image_url: profileImageUrl || '' });
|
||||
} catch {
|
||||
toast.error('Failed to update profile picture');
|
||||
throw new Error('Failed to update profile picture');
|
||||
}
|
||||
};
|
||||
|
||||
const handleIconSave = async (iconName: string | null, iconColor: string, iconBackground: string) => {
|
||||
try {
|
||||
await saveField({ icon_name: iconName, icon_color: iconColor, icon_background: iconBackground });
|
||||
toast.success('Agent icon updated');
|
||||
} catch {
|
||||
toast.error('Failed to update agent icon');
|
||||
throw new Error('Failed to update agent icon');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSystemPromptSave = async (system_prompt: string) => {
|
||||
try {
|
||||
await saveField({ system_prompt });
|
||||
toast.success('System prompt updated');
|
||||
} catch {
|
||||
toast.error('Failed to update system prompt');
|
||||
throw new Error('Failed to update system prompt');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelSave = async (model: string) => {
|
||||
try {
|
||||
await saveField({ model });
|
||||
toast.success('Model updated');
|
||||
} catch {
|
||||
toast.error('Failed to update model');
|
||||
throw new Error('Failed to update model');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToolsSave = async (agentpress_tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
|
||||
try {
|
||||
await saveField({ agentpress_tools });
|
||||
toast.success('Tools updated');
|
||||
} catch {
|
||||
toast.error('Failed to update tools');
|
||||
throw new Error('Failed to update tools');
|
||||
}
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
mutationsRef.current.export.mutate(agentIdRef.current);
|
||||
};
|
||||
|
||||
const { hasUnsavedChanges, isCurrentVersion } = React.useMemo(() => {
|
||||
const formDataStr = JSON.stringify(formData);
|
||||
|
@ -234,141 +368,33 @@ function AgentConfigurationContent() {
|
|||
|
||||
const handleActivateVersion = async (versionId: string) => {
|
||||
try {
|
||||
await activateVersionMutation.mutateAsync({ agentId, versionId });
|
||||
router.push(`/agents/config/${agentId}`);
|
||||
await mutationsRef.current.activate.mutateAsync({ agentId: agentIdRef.current, versionId });
|
||||
router.push(`/agents/config/${agentIdRef.current}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to activate version:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// OPTIMIZED: Simplified save with stable reference
|
||||
const handleSave = useCallback(async () => {
|
||||
if (hasUnsavedChanges) {
|
||||
const currentFormData = formData;
|
||||
const hasChanges = JSON.stringify(currentFormData) !== JSON.stringify(originalData);
|
||||
|
||||
if (hasChanges) {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
is_default: formData.is_default,
|
||||
profile_image_url: formData.profile_image_url,
|
||||
icon_name: formData.icon_name,
|
||||
icon_color: formData.icon_color,
|
||||
icon_background: formData.icon_background,
|
||||
system_prompt: formData.system_prompt,
|
||||
agentpress_tools: formData.agentpress_tools,
|
||||
configured_mcps: formData.configured_mcps,
|
||||
custom_mcps: formData.custom_mcps,
|
||||
await mutationsRef.current.updateAgent.mutateAsync({
|
||||
agentId: agentIdRef.current,
|
||||
...currentFormData,
|
||||
});
|
||||
|
||||
setOriginalData(formData);
|
||||
setOriginalData(currentFormData);
|
||||
toast.success('Agent updated successfully');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update agent');
|
||||
console.error('Failed to save agent:', error);
|
||||
}
|
||||
}
|
||||
}, [agentId, formData, hasUnsavedChanges, updateAgentMutation]);
|
||||
|
||||
const handleNameSave = useCallback(async (name: string) => {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
name,
|
||||
});
|
||||
|
||||
setFormData(prev => ({ ...prev, name }));
|
||||
setOriginalData(prev => ({ ...prev, name }));
|
||||
toast.success('Agent name updated');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update agent name');
|
||||
throw error;
|
||||
}
|
||||
}, [agentId, updateAgentMutation]);
|
||||
|
||||
const handleProfileImageSave = useCallback(async (profileImageUrl: string | null) => {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
profile_image_url: profileImageUrl || '',
|
||||
});
|
||||
|
||||
setFormData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
|
||||
setOriginalData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
|
||||
} catch (error) {
|
||||
toast.error('Failed to update profile picture');
|
||||
throw error;
|
||||
}
|
||||
}, [agentId, updateAgentMutation]);
|
||||
|
||||
const handleIconSave = useCallback(async (iconName: string | null, iconColor: string, iconBackground: string) => {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
icon_name: iconName,
|
||||
icon_color: iconColor,
|
||||
icon_background: iconBackground,
|
||||
});
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
icon_name: iconName,
|
||||
icon_color: iconColor,
|
||||
icon_background: iconBackground,
|
||||
}));
|
||||
setOriginalData(prev => ({
|
||||
...prev,
|
||||
icon_name: iconName,
|
||||
icon_color: iconColor,
|
||||
icon_background: iconBackground,
|
||||
}));
|
||||
toast.success('Agent icon updated');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update agent icon');
|
||||
throw error;
|
||||
}
|
||||
}, [agentId, updateAgentMutation]);
|
||||
|
||||
const handleSystemPromptSave = useCallback(async (value: string) => {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
system_prompt: value,
|
||||
});
|
||||
|
||||
setFormData(prev => ({ ...prev, system_prompt: value }));
|
||||
setOriginalData(prev => ({ ...prev, system_prompt: value }));
|
||||
toast.success('System prompt updated');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update system prompt');
|
||||
throw error;
|
||||
}
|
||||
}, [agentId, updateAgentMutation]);
|
||||
|
||||
const handleModelSave = useCallback(async (model: string) => {
|
||||
try {
|
||||
setFormData(prev => ({ ...prev, model }));
|
||||
setOriginalData(prev => ({ ...prev, model }));
|
||||
toast.success('Model updated');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update model');
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToolsSave = useCallback(async (tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
agentpress_tools: tools,
|
||||
});
|
||||
|
||||
setFormData(prev => ({ ...prev, agentpress_tools: tools }));
|
||||
setOriginalData(prev => ({ ...prev, agentpress_tools: tools }));
|
||||
toast.success('Tools updated');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update tools');
|
||||
throw error;
|
||||
}
|
||||
}, [agentId, updateAgentMutation]);
|
||||
}, []); // Using snapshot of formData in function instead of dependency
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
@ -572,7 +598,8 @@ export default function AgentConfigurationPage() {
|
|||
handleWelcomeDecline,
|
||||
} = useAgentConfigTour();
|
||||
|
||||
const handleTourCallback = useCallback((data: CallBackProps) => {
|
||||
// OPTIMIZED: Simple function instead of useCallback with stable dependencies
|
||||
const handleTourCallback = (data: CallBackProps) => {
|
||||
const { status, type, index } = data;
|
||||
|
||||
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
|
||||
|
@ -580,7 +607,7 @@ export default function AgentConfigurationPage() {
|
|||
} else if (type === 'step:after') {
|
||||
setStepIndex(index + 1);
|
||||
}
|
||||
}, [stopTour, setStepIndex]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -45,18 +45,23 @@ export const AgentMCPConfiguration: React.FC<AgentMCPConfigurationProps> = ({
|
|||
};
|
||||
}
|
||||
|
||||
// Map 'sse' backend type to 'http' for frontend display
|
||||
const displayType = customMcp.type === 'sse' ? 'http' : (customMcp.type || customMcp.customType);
|
||||
|
||||
return {
|
||||
name: customMcp.name,
|
||||
qualifiedName: customMcp.qualifiedName || `custom_${customMcp.type || customMcp.customType}_${customMcp.name.replace(' ', '_').toLowerCase()}`,
|
||||
qualifiedName: customMcp.qualifiedName || `custom_${displayType}_${customMcp.name.replace(' ', '_').toLowerCase()}`,
|
||||
config: customMcp.config,
|
||||
enabledTools: customMcp.enabledTools,
|
||||
isCustom: true,
|
||||
customType: customMcp.type || customMcp.customType
|
||||
customType: displayType
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
const handleConfigurationChange = (mcps: any[]) => {
|
||||
console.log('[AgentMCPConfiguration] Configuration changed:', mcps);
|
||||
|
||||
const configured = mcps.filter(mcp => !mcp.isCustom);
|
||||
const custom = mcps
|
||||
.filter(mcp => mcp.isCustom)
|
||||
|
@ -71,15 +76,20 @@ export const AgentMCPConfiguration: React.FC<AgentMCPConfigurationProps> = ({
|
|||
};
|
||||
}
|
||||
|
||||
// Map 'http' to 'sse' for backend compatibility
|
||||
const backendType = mcp.customType === 'http' ? 'sse' : mcp.customType;
|
||||
|
||||
return {
|
||||
name: mcp.name,
|
||||
type: mcp.customType,
|
||||
type: backendType,
|
||||
customType: mcp.customType,
|
||||
config: mcp.config,
|
||||
enabledTools: mcp.enabledTools
|
||||
};
|
||||
});
|
||||
|
||||
console.log('[AgentMCPConfiguration] Sending to parent - configured:', configured, 'custom:', custom);
|
||||
|
||||
onMCPChange({
|
||||
configured_mcps: configured,
|
||||
custom_mcps: custom
|
||||
|
|
|
@ -7,14 +7,14 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
|||
import { Search, Zap, X, Settings, ChevronDown, ChevronUp, Loader2, Server } from 'lucide-react';
|
||||
import { useComposioCategories, useComposioToolkitsInfinite } from '@/hooks/react-query/composio/use-composio';
|
||||
import { useComposioProfiles } from '@/hooks/react-query/composio/use-composio-profiles';
|
||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
|
||||
import { ComposioConnector } from './composio-connector';
|
||||
import { ComposioToolsManager } from './composio-tools-manager';
|
||||
import type { ComposioToolkit, ComposioProfile } from '@/hooks/react-query/composio/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
// import { AgentSelector } from '../../thread/chat-input/agent-selector';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { CustomMCPDialog } from '../mcp/custom-mcp-dialog';
|
||||
|
||||
|
@ -168,6 +168,7 @@ const ConnectedAppCard = ({
|
|||
size="sm"
|
||||
onClick={() => onManageTools(connectedApp)}
|
||||
disabled={isUpdating}
|
||||
type="button"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
|
@ -311,7 +312,7 @@ export const ComposioRegistry: React.FC<ComposioRegistryProps> = ({
|
|||
|
||||
const currentAgentId = selectedAgentId ?? internalSelectedAgentId;
|
||||
const { data: agent, isLoading: isLoadingAgent } = useAgent(currentAgentId || '');
|
||||
const { mutate: updateAgent, isPending: isUpdatingAgent } = useUpdateAgent();
|
||||
const { mutate: updateAgent, isPending: isUpdatingAgent } = useUpdateAgentMCPs(); // Use the MCP-specific hook
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
if (onAgentChange) {
|
||||
|
@ -429,7 +430,8 @@ export const ComposioRegistry: React.FC<ComposioRegistryProps> = ({
|
|||
return new Promise((resolve, reject) => {
|
||||
updateAgent({
|
||||
agentId: currentAgentId,
|
||||
custom_mcps: updatedCustomMcps
|
||||
custom_mcps: updatedCustomMcps,
|
||||
replace_mcps: true // Use replace mode to ensure proper updates
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
toast.success(`Custom MCP "${customConfig.name}" added successfully`);
|
||||
|
|
|
@ -127,6 +127,7 @@ const MCPConfigurationItem: React.FC<{
|
|||
className="h-8 w-8 p-0"
|
||||
onClick={() => onConfigureTools(index)}
|
||||
title="Configure tools"
|
||||
type="button"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
|
@ -137,6 +138,7 @@ const MCPConfigurationItem: React.FC<{
|
|||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
onClick={() => onRemove(index)}
|
||||
title="Remove integration"
|
||||
type="button"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
|
@ -196,7 +198,7 @@ export const ConfiguredMcpList: React.FC<ConfiguredMcpListProps> = ({
|
|||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-destructive hover:bg-destructive/90"
|
||||
className="bg-destructive hover:bg-destructive/90 text-white"
|
||||
>
|
||||
Remove Integration
|
||||
</AlertDialogAction>
|
||||
|
|
|
@ -407,15 +407,16 @@ export const CustomMCPDialog: React.FC<CustomMCPDialogProps> = ({
|
|||
<DialogFooter className="flex-shrink-0 pt-4">
|
||||
{step === 'tools' ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSaving}>
|
||||
<Button variant="outline" onClick={handleBack} disabled={isSaving} type="button">
|
||||
Back
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving}>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving} type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleToolsNext}
|
||||
disabled={selectedTools.size === 0 || isSaving}
|
||||
type="button"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
|
@ -429,12 +430,13 @@ export const CustomMCPDialog: React.FC<CustomMCPDialogProps> = ({
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={validateAndDiscoverTools}
|
||||
disabled={!configText.trim() || !manualServerName.trim() || isValidating}
|
||||
type="button"
|
||||
>
|
||||
{isValidating ? (
|
||||
<>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Zap, Server, Store } from 'lucide-react'
|
||||
import { Zap, Server, Store, Settings } from 'lucide-react'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { MCPConfigurationProps, MCPConfiguration as MCPConfigurationType } from './types';
|
||||
import { ConfiguredMcpList } from './configured-mcp-list';
|
||||
|
@ -29,10 +29,6 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
const [selectedAgentId, setSelectedAgentId] = useState<string | undefined>(agentId);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedAgentId(agentId);
|
||||
}, [agentId]);
|
||||
|
||||
const handleAgentChange = (newAgentId: string | undefined) => {
|
||||
setSelectedAgentId(newAgentId);
|
||||
};
|
||||
|
@ -64,8 +60,7 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
};
|
||||
|
||||
const handleRemoveMCP = (index: number) => {
|
||||
const newMCPs = [...configuredMCPs];
|
||||
newMCPs.splice(index, 1);
|
||||
const newMCPs = configuredMCPs.filter((_, i) => i !== index);
|
||||
onConfigurationChange(newMCPs);
|
||||
};
|
||||
|
||||
|
@ -84,8 +79,9 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
|
||||
const handleToolsSelected = (profileId: string, selectedTools: string[], appName: string, appSlug: string) => {
|
||||
setShowRegistryDialog(false);
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['agent', selectedAgentId] });
|
||||
if (selectedAgentId) {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents', 'detail', selectedAgentId] });
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['composio', 'profiles'] });
|
||||
toast.success(`Connected ${appName} integration!`);
|
||||
};
|
||||
|
@ -107,11 +103,11 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} size="sm" variant="default" className="gap-2">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} size="sm" variant="default" className="gap-2" type="button">
|
||||
<Store className="h-4 w-4" />
|
||||
Browse Apps
|
||||
</Button>
|
||||
<Button onClick={() => setShowCustomDialog(true)} size="sm" variant="outline" className="gap-2">
|
||||
<Button onClick={() => setShowCustomDialog(true)} size="sm" variant="outline" className="gap-2" type="button">
|
||||
<Server className="h-4 w-4" />
|
||||
Custom MCP
|
||||
</Button>
|
||||
|
@ -155,8 +151,9 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
onToolsSelected={handleToolsSelected}
|
||||
onClose={() => {
|
||||
setShowRegistryDialog(false);
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['agent', selectedAgentId] });
|
||||
if (selectedAgentId) {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents', 'detail', selectedAgentId] });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { createMutationHook } from '@/hooks/use-query';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { AgentUpdateRequest, updateAgent } from './utils';
|
||||
import { agentKeys } from './keys';
|
||||
|
||||
export const useUpdateAgentMCPs = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return createMutationHook(
|
||||
({ agentId, ...data }: { agentId: string } & AgentUpdateRequest) =>
|
||||
updateAgent(agentId, data),
|
||||
{
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.setQueryData(agentKeys.detail(variables.agentId), data);
|
||||
},
|
||||
}
|
||||
)();
|
||||
};
|
|
@ -163,6 +163,8 @@ export type AgentUpdateRequest = {
|
|||
icon_name?: string | null;
|
||||
icon_color?: string | null;
|
||||
icon_background?: string | null;
|
||||
// MCP replacement flag
|
||||
replace_mcps?: boolean;
|
||||
};
|
||||
|
||||
export const getAgents = async (params: AgentsParams = {}): Promise<AgentsResponse> => {
|
||||
|
|
Loading…
Reference in New Issue