From 50b6ad518921d663c4f664c16760c6c5501e354e Mon Sep 17 00:00:00 2001 From: Saumya Date: Wed, 6 Aug 2025 11:18:06 +0530 Subject: [PATCH] improve composio UC --- .../credential_profile_tool.py | 2 - .../connected_account_service.py | 22 +- .../mcp_server_service.py | 10 +- .../(dashboard)/settings/credentials/page.tsx | 2 +- .../composio/composio-connections-section.tsx | 37 +- .../agents/composio/composio-connector.tsx | 111 +++-- .../agents/composio/composio-registry.tsx | 381 +++++++++++++++--- .../composio/composio-tools-manager.tsx | 10 +- 8 files changed, 448 insertions(+), 127 deletions(-) diff --git a/backend/agent/tools/agent_builder_tools/credential_profile_tool.py b/backend/agent/tools/agent_builder_tools/credential_profile_tool.py index eed3e0b2..4a662d44 100644 --- a/backend/agent/tools/agent_builder_tools/credential_profile_tool.py +++ b/backend/agent/tools/agent_builder_tools/credential_profile_tool.py @@ -121,8 +121,6 @@ class CredentialProfileTool(AgentBuilderBaseTool): save_as_profile=True ) - print("[DEBUG] create_credential_profile result:", result) - response_data = { "message": f"Successfully created credential profile '{profile_name}' for {result.toolkit.name}", "profile": { diff --git a/backend/composio_integration/connected_account_service.py b/backend/composio_integration/connected_account_service.py index d124e082..f7797a16 100644 --- a/backend/composio_integration/connected_account_service.py +++ b/backend/composio_integration/connected_account_service.py @@ -26,42 +26,33 @@ class ConnectedAccountService: self.client = ComposioClient.get_client(api_key) def _extract_deprecated_value(self, deprecated_obj) -> Optional[bool]: - """Extract boolean value from Composio SDK's Deprecated object""" if deprecated_obj is None: return None - # If it's already a boolean, return it if isinstance(deprecated_obj, bool): return deprecated_obj - - # If it's a Composio Deprecated object, try to extract meaningful info + if hasattr(deprecated_obj, '__dict__'): - # Check if it has any deprecation info - if so, consider it deprecated deprecated_dict = deprecated_obj.__dict__ if deprecated_dict: return True - # Default to not deprecated return False def _extract_val_dict(self, val_obj) -> Dict[str, Any]: - """Extract dictionary from Composio SDK's val object""" if val_obj is None: return {} - # If it's already a dict, return it if isinstance(val_obj, dict): return val_obj - # If it's a Pydantic model, convert it to dict if hasattr(val_obj, 'model_dump'): return val_obj.model_dump() elif hasattr(val_obj, 'dict'): return val_obj.dict() elif hasattr(val_obj, '__dict__'): return val_obj.__dict__ - - # Fallback to empty dict + return {} async def create_connected_account( @@ -85,16 +76,13 @@ class ConnectedAccountService: } ) - # Access Pydantic model attributes directly connection_data_obj = getattr(response, 'connection_data', None) if not connection_data_obj: - # Try alternative attribute names connection_data_obj = getattr(response, 'connectionData', None) if connection_data_obj and hasattr(connection_data_obj, '__dict__'): connection_data_dict = connection_data_obj.__dict__ - # Extract val field properly - it might be a Pydantic object val_obj = connection_data_dict.get('val', {}) val_dict = self._extract_val_dict(val_obj) @@ -105,7 +93,6 @@ class ConnectedAccountService: else: connection_data = ConnectionState() - # Handle the deprecated field properly deprecated_obj = getattr(response, 'deprecated', None) deprecated_value = self._extract_deprecated_value(deprecated_obj) @@ -136,7 +123,6 @@ class ConnectedAccountService: if not response: return None - # Access Pydantic model attributes directly connection_data_obj = getattr(response, 'connection_data', None) if not connection_data_obj: connection_data_obj = getattr(response, 'connectionData', None) @@ -144,7 +130,6 @@ class ConnectedAccountService: if connection_data_obj and hasattr(connection_data_obj, '__dict__'): connection_data_dict = connection_data_obj.__dict__ - # Extract val field properly - it might be a Pydantic object val_obj = connection_data_dict.get('val', {}) val_dict = self._extract_val_dict(val_obj) @@ -155,7 +140,6 @@ class ConnectedAccountService: else: connection_data = ConnectionState() - # Handle the deprecated field properly deprecated_obj = getattr(response, 'deprecated', None) deprecated_value = self._extract_deprecated_value(deprecated_obj) @@ -213,7 +197,6 @@ class ConnectedAccountService: if connection_data_obj and hasattr(connection_data_obj, '__dict__'): connection_data_dict = connection_data_obj.__dict__ - # Extract val field properly - it might be a Pydantic object val_obj = connection_data_dict.get('val', {}) val_dict = self._extract_val_dict(val_obj) @@ -224,7 +207,6 @@ class ConnectedAccountService: else: connection_data = ConnectionState() - # Handle the deprecated field properly deprecated_obj = getattr(item, 'deprecated', None) deprecated_value = self._extract_deprecated_value(deprecated_obj) diff --git a/backend/composio_integration/mcp_server_service.py b/backend/composio_integration/mcp_server_service.py index 35055f5d..80bf12e3 100644 --- a/backend/composio_integration/mcp_server_service.py +++ b/backend/composio_integration/mcp_server_service.py @@ -142,18 +142,15 @@ class MCPServerService: if user_ids: request_data["user_ids"] = user_ids - - # Try different possible API paths + try: response = self.client.mcp.generate_mcp_url(**request_data) except AttributeError: try: response = self.client.mcp.generate.url(**request_data) except AttributeError: - # Fallback: try direct method call response = self.client.generate_mcp_url(**request_data) - # Access Pydantic model attributes directly mcp_url_response = MCPUrlResponse( mcp_url=response.mcp_url, connected_account_urls=getattr(response, 'connected_account_urls', []), @@ -171,17 +168,14 @@ class MCPServerService: try: logger.info(f"Fetching MCP server: {mcp_server_id}") - # Try different possible API paths try: response = self.client.mcp.get(mcp_server_id) except AttributeError: - # Fallback: try direct method call response = self.client.get_mcp_server(mcp_server_id) if not response: return None - # Access Pydantic model attributes directly commands_obj = getattr(response, 'commands', None) commands = MCPCommands( @@ -211,11 +205,9 @@ class MCPServerService: try: logger.info("Listing MCP servers") - # Try different possible API paths try: response = self.client.mcp.list() except AttributeError: - # Fallback: try direct method call response = self.client.list_mcp_servers() mcp_servers = [] diff --git a/frontend/src/app/(dashboard)/settings/credentials/page.tsx b/frontend/src/app/(dashboard)/settings/credentials/page.tsx index e933d3c1..c22fc160 100644 --- a/frontend/src/app/(dashboard)/settings/credentials/page.tsx +++ b/frontend/src/app/(dashboard)/settings/credentials/page.tsx @@ -44,7 +44,7 @@ export default function AppProfilesPage() {
- Composio Credentials + App Credentials
diff --git a/frontend/src/components/agents/composio/composio-connections-section.tsx b/frontend/src/components/agents/composio/composio-connections-section.tsx index bc2e79d7..0de6ba57 100644 --- a/frontend/src/components/agents/composio/composio-connections-section.tsx +++ b/frontend/src/components/agents/composio/composio-connections-section.tsx @@ -35,8 +35,10 @@ import { XCircle, } from 'lucide-react'; import { useComposioCredentialsProfiles, useComposioMcpUrl } from '@/hooks/react-query/composio/use-composio-profiles'; +import { ComposioRegistry } from './composio-registry'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; +import { useQueryClient } from '@tanstack/react-query'; import type { ComposioProfileSummary, ComposioToolkitGroup } from '@/hooks/react-query/composio/utils'; interface ComposioConnectionsSectionProps { @@ -367,6 +369,8 @@ export const ComposioConnectionsSection: React.FC { const { data: toolkits, isLoading, error } = useComposioCredentialsProfiles(); const [searchQuery, setSearchQuery] = useState(''); + const [showRegistry, setShowRegistry] = useState(false); + const queryClient = useQueryClient(); const filteredToolkits = useMemo(() => { if (!toolkits || !searchQuery.trim()) return toolkits || []; @@ -405,6 +409,12 @@ export const ComposioConnectionsSection: React.FC { + setShowRegistry(false); + queryClient.invalidateQueries({ queryKey: ['composio', 'profiles'] }); + toast.success(`Successfully connected ${appName}!`); + }; + if (isLoading) { return (
@@ -448,15 +458,13 @@ export const ComposioConnectionsSection: React.FC
-

No Composio Connections

+

No Connections

- You haven't connected any Composio applications yet. + You haven't connected any applications yet.

-
@@ -477,7 +485,7 @@ export const ComposioConnectionsSection: React.FC
); }; \ No newline at end of file diff --git a/frontend/src/components/agents/composio/composio-connector.tsx b/frontend/src/components/agents/composio/composio-connector.tsx index 77d804db..9cbbc74b 100644 --- a/frontend/src/components/agents/composio/composio-connector.tsx +++ b/frontend/src/components/agents/composio/composio-connector.tsx @@ -88,7 +88,7 @@ const getStepIndex = (step: Step): number => { const StepIndicator = ({ currentStep, mode }: { currentStep: Step; mode: 'full' | 'profile-only' }) => { const currentIndex = getStepIndex(currentStep); const visibleSteps = mode === 'profile-only' - ? stepConfigs.filter(step => step.id !== Step.ToolsSelection) + ? stepConfigs.filter(step => step.id !== Step.ToolsSelection && step.id !== Step.ProfileSelect) : stepConfigs; const visibleCurrentIndex = visibleSteps.findIndex(step => step.id === currentStep); @@ -274,7 +274,7 @@ export const ComposioConnector: React.FC = ({ useEffect(() => { if (open) { - setStep(Step.ProfileSelect); + setStep(mode === 'profile-only' ? Step.ProfileCreate : Step.ProfileSelect); setProfileName(`${app.name} Profile`); setSelectedProfileId(''); setSelectedProfile(null); @@ -287,7 +287,7 @@ export const ComposioConnector: React.FC = ({ setAvailableTools([]); setToolsError(null); } - }, [open, app.name]); + }, [open, app.name, mode]); useEffect(() => { if (step === Step.ToolsSelection && selectedProfile) { @@ -485,7 +485,11 @@ export const ComposioConnector: React.FC = ({ const handleBack = () => { switch (step) { case Step.ProfileCreate: - navigateToStep(Step.ProfileSelect); + if (mode === 'profile-only') { + onOpenChange(false); + } else { + navigateToStep(Step.ProfileSelect); + } break; case Step.Connecting: navigateToStep(Step.ProfileCreate); @@ -590,30 +594,61 @@ export const ComposioConnector: React.FC = ({ transition={{ duration: 0.3, ease: "easeInOut" }} className="space-y-6" > -
- - + + + + + {existingProfiles.map((profile) => ( + +
+ {app.logo ? ( + {app.name} + ) : ( +
+ {app.name.charAt(0)} +
+ )} +
+
{profile.profile_name}
+
+ Created {formatDistanceToNow(new Date(profile.created_at), { addSuffix: true })} +
+
-
- - ))} - -
- - Create New Profile + + ))} + + +
+ )} +
+
+
+ + {existingProfiles.length > 0 ? 'or' : ''} + +
+
+ +
@@ -624,14 +659,16 @@ export const ComposioConnector: React.FC = ({ > Cancel - + {existingProfiles.length > 0 && ( + + )}
)} @@ -754,11 +791,11 @@ export const ComposioConnector: React.FC = ({ className="text-center py-8" >
-
- +
+
-
+

Successfully Connected!

Your {app.name} integration is ready. diff --git a/frontend/src/components/agents/composio/composio-registry.tsx b/frontend/src/components/agents/composio/composio-registry.tsx index 0a16dcbb..0184eab1 100644 --- a/frontend/src/components/agents/composio/composio-registry.tsx +++ b/frontend/src/components/agents/composio/composio-registry.tsx @@ -4,16 +4,19 @@ import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { Search, Zap, X } from 'lucide-react'; +import { Search, Zap, X, Settings, ChevronDown, ChevronUp } from 'lucide-react'; import { useComposioToolkits, useComposioCategories } from '@/hooks/react-query/composio/use-composio'; import { useComposioProfiles } from '@/hooks/react-query/composio/use-composio-profiles'; -import { useAgent } from '@/hooks/react-query/agents/use-agents'; +import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents'; 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 { Switch } from '@/components/ui/switch'; const CATEGORY_EMOJIS: Record = { 'popular': '🔥', @@ -26,6 +29,16 @@ const CATEGORY_EMOJIS: Record = { 'scheduling': '📅', }; +interface ConnectedApp { + toolkit: ComposioToolkit; + profile: ComposioProfile; + mcpConfig: { + name: string; + type: string; + config: Record; + enabledTools: string[]; + }; +} interface ComposioRegistryProps { onToolsSelected?: (profileId: string, selectedTools: string[], appName: string, appSlug: string) => void; @@ -37,6 +50,52 @@ interface ComposioRegistryProps { onAgentChange?: (agentId: string | undefined) => void; } +// Helper function to get agent-specific connected apps +const getAgentConnectedApps = ( + agent: any, + profiles: ComposioProfile[], + toolkits: ComposioToolkit[] +): ConnectedApp[] => { + if (!agent?.custom_mcps || !profiles?.length || !toolkits?.length) return []; + + const connectedApps: ConnectedApp[] = []; + + agent.custom_mcps.forEach((mcpConfig: any) => { + // Check if this is a Composio MCP by looking for profile_id in config + if (mcpConfig.config?.profile_id) { + const profile = profiles.find(p => p.profile_id === mcpConfig.config.profile_id); + const toolkit = toolkits.find(t => t.slug === profile?.toolkit_slug); + + if (profile && toolkit) { + connectedApps.push({ + toolkit, + profile, + mcpConfig + }); + } + } + }); + + return connectedApps; +}; + +// Helper function to check if an app is connected to the agent +const isAppConnectedToAgent = ( + agent: any, + appSlug: string, + profiles: ComposioProfile[] +): boolean => { + if (!agent?.custom_mcps) return false; + + return agent.custom_mcps.some((mcpConfig: any) => { + if (mcpConfig.config?.profile_id) { + const profile = profiles.find(p => p.profile_id === mcpConfig.config.profile_id); + return profile?.toolkit_slug === appSlug; + } + return false; + }); +}; + const AppCardSkeleton = () => (

@@ -57,16 +116,100 @@ const AppCardSkeleton = () => (
); -const AppCard = ({ app, profiles, onConnect, onConfigure }: { +const ConnectedAppSkeleton = () => ( +
+
+ +
+ + +
+ +
+
+ +
+
+); + +const ConnectedAppCard = ({ + connectedApp, + onToggleTools, + onConfigure, + onManageTools, + isUpdating +}: { + connectedApp: ConnectedApp; + onToggleTools: (profileId: string, enabled: boolean) => void; + onConfigure: (app: ComposioToolkit, profile: ComposioProfile) => void; + onManageTools: (connectedApp: ConnectedApp) => void; + isUpdating: boolean; +}) => { + const { toolkit, profile, mcpConfig } = connectedApp; + const hasEnabledTools = mcpConfig.enabledTools && mcpConfig.enabledTools.length > 0; + + return ( +
+
+ {toolkit.logo ? ( + {toolkit.name} + ) : ( +
+ {toolkit.name.charAt(0)} +
+ )} +
+

{toolkit.name}

+

+ Connected as "{profile.profile_name}" +

+
+
+ +
+
+ +
+
+
+
+ {hasEnabledTools ? `${mcpConfig.enabledTools.length} tools enabled` : 'Connected (no tools)'} +
+
+
+
+ ); +}; + +const AppCard = ({ app, profiles, onConnect, onConfigure, isConnectedToAgent, currentAgentId, mode }: { app: ComposioToolkit; profiles: ComposioProfile[]; onConnect: () => void; onConfigure: (profile: ComposioProfile) => void; + isConnectedToAgent: boolean; + currentAgentId?: string; + mode?: 'full' | 'profile-only'; }) => { const connectedProfiles = profiles.filter(p => p.is_connected); + const canConnect = mode === 'profile-only' ? true : (!isConnectedToAgent && currentAgentId); return ( -
0 ? () => onConfigure(connectedProfiles[0]) : onConnect} className="group border bg-card hover:bg-muted rounded-2xl p-4 transition-all duration-200"> +
0 ? () => onConfigure(connectedProfiles[0]) : onConnect) : undefined} + className={cn( + "group border bg-card rounded-2xl p-4 transition-all duration-200", + canConnect ? "hover:bg-muted cursor-pointer" : "opacity-60 cursor-not-allowed" + )} + >
{app.logo ? ( {app.name} @@ -99,11 +242,32 @@ const AppCard = ({ app, profiles, onConnect, onConfigure }: { )}
- {connectedProfiles.length > 0 && ( + {mode === 'profile-only' ? ( +
+
+
+ {connectedProfiles.length > 0 ? `${connectedProfiles.length} existing profile${connectedProfiles.length !== 1 ? 's' : ''}` : 'Click to connect'} +
+
+ ) : isConnectedToAgent ? ( +
+
+
+ Connected to this agent +
+
+ ) : connectedProfiles.length > 0 ? (
- Connected ({connectedProfiles.length}) + Profile available ({connectedProfiles.length}) +
+
+ ) : ( +
+
+
+ Not connected
)} @@ -125,16 +289,20 @@ export const ComposioRegistry: React.FC = ({ const [selectedCategory, setSelectedCategory] = useState(''); const [selectedApp, setSelectedApp] = useState(null); const [showConnector, setShowConnector] = useState(false); + const [showConnectedApps, setShowConnectedApps] = useState(true); + const [showToolsManager, setShowToolsManager] = useState(false); + const [selectedConnectedApp, setSelectedConnectedApp] = useState(null); const [internalSelectedAgentId, setInternalSelectedAgentId] = useState(selectedAgentId); const queryClient = useQueryClient(); const { data: categoriesData, isLoading: isLoadingCategories } = useComposioCategories(); const { data: toolkits, isLoading } = useComposioToolkits(search, selectedCategory); - const { data: profiles } = useComposioProfiles(); + const { data: profiles, isLoading: isLoadingProfiles } = useComposioProfiles(); const currentAgentId = selectedAgentId ?? internalSelectedAgentId; - const { data: agent } = useAgent(currentAgentId || ''); + const { data: agent, isLoading: isLoadingAgent } = useAgent(currentAgentId || ''); + const { mutate: updateAgent, isPending: isUpdatingAgent } = useUpdateAgent(); const handleAgentSelect = (agentId: string | undefined) => { if (onAgentChange) { @@ -157,13 +325,20 @@ export const ComposioRegistry: React.FC = ({ return grouped; }, [profiles]); + const connectedApps = useMemo(() => { + if (!currentAgentId || !agent) return []; + return getAgentConnectedApps(agent, profiles || [], toolkits?.toolkits || []); + }, [agent, profiles, toolkits, currentAgentId]); + + const isLoadingConnectedApps = currentAgentId && (isLoadingAgent || isLoadingProfiles || isLoading); + const filteredToolkits = useMemo(() => { if (!toolkits?.toolkits) return []; return toolkits.toolkits; }, [toolkits]); const handleConnect = (app: ComposioToolkit) => { - if (!currentAgentId && showAgentSelector) { + if (mode !== 'profile-only' && !currentAgentId && showAgentSelector) { toast.error('Please select an agent first'); return; } @@ -172,7 +347,7 @@ export const ComposioRegistry: React.FC = ({ }; const handleConfigure = (app: ComposioToolkit, profile: ComposioProfile) => { - if (!currentAgentId) { + if (mode !== 'profile-only' && !currentAgentId) { toast.error('Please select an agent first'); return; } @@ -180,6 +355,37 @@ export const ComposioRegistry: React.FC = ({ setShowConnector(true); }; + const handleToggleTools = (profileId: string, enabled: boolean) => { + if (!currentAgentId || !agent) return; + + const updatedCustomMcps = agent.custom_mcps?.map((mcpConfig: any) => { + if (mcpConfig.config?.profile_id === profileId) { + return { + ...mcpConfig, + enabledTools: enabled ? mcpConfig.enabledTools || [] : [] + }; + } + return mcpConfig; + }) || []; + + updateAgent({ + agentId: currentAgentId, + custom_mcps: updatedCustomMcps + }, { + onSuccess: () => { + toast.success(enabled ? 'Tools enabled' : 'Tools disabled'); + }, + onError: (error: any) => { + toast.error(error.message || 'Failed to update tools'); + } + }); + }; + + const handleManageTools = (connectedApp: ConnectedApp) => { + setSelectedConnectedApp(connectedApp); + setShowToolsManager(true); + }; + const handleConnectionComplete = (profileId: string, appName: string, appSlug: string) => { setShowConnector(false); queryClient.invalidateQueries({ queryKey: ['composio', 'profiles'] }); @@ -251,9 +457,14 @@ export const ComposioRegistry: React.FC = ({
-

App Integrations

+

+ {mode === 'profile-only' ? 'Connect New App' : 'App Integrations'} +

- Connect your favorite apps powered by Composio + {mode === 'profile-only' + ? 'Create a connection profile for your favorite apps' + : `Connect your favorite apps with ${currentAgentId ? 'this agent' : 'your agent'}` + }

@@ -264,11 +475,6 @@ export const ComposioRegistry: React.FC = ({ isSunaAgent={agent?.metadata?.is_suna_default} /> )} - {onClose && ( - - )}
@@ -301,42 +507,103 @@ export const ComposioRegistry: React.FC = ({
-
- {isLoading ? ( -
- {Array.from({ length: 12 }).map((_, i) => ( - - ))} -
- ) : filteredToolkits.length === 0 ? ( -
-
- -
-

No apps found

-

- {search ? `No apps match "${search}"` : 'No apps available in this category'} -

-
- ) : ( -
- {filteredToolkits.map((app) => ( - handleConnect(app)} - onConfigure={(profile) => handleConfigure(app, profile)} - /> - ))} -
+
+ {currentAgentId && ( + + +
+
+

Connected to this agent

+ {isLoadingConnectedApps ? ( + + ) : connectedApps.length > 0 && ( + + {connectedApps.length} + + )} +
+ {showConnectedApps ? ( + + ) : ( + + )} +
+
+ + {isLoadingConnectedApps ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+ ) : connectedApps.length === 0 ? ( +
+
+ +
+

No connected apps

+

Connect apps below to manage tools for this agent.

+
+ ) : ( +
+ {connectedApps.map((connectedApp) => ( + + ))} +
+ )} +
+
)} +
+

+ {currentAgentId ? 'Available Apps' : 'Browse Apps'} +

+ + {isLoading ? ( +
+ {Array.from({ length: 12 }).map((_, i) => ( + + ))} +
+ ) : filteredToolkits.length === 0 ? ( +
+
+ +
+

No apps found

+

+ {search ? `No apps match "${search}"` : 'No apps available in this category'} +

+
+ ) : ( +
+ {filteredToolkits.map((app) => ( + handleConnect(app)} + onConfigure={(profile) => handleConfigure(app, profile)} + isConnectedToAgent={isAppConnectedToAgent(agent, app.slug, profiles || [])} + currentAgentId={currentAgentId} + mode={mode} + /> + ))} +
+ )} +
- {selectedApp && ( = ({ open={showConnector} onOpenChange={setShowConnector} onComplete={handleConnectionComplete} + mode={mode} + /> + )} + + {selectedConnectedApp && currentAgentId && ( + { + queryClient.invalidateQueries({ queryKey: ['agents', currentAgentId] }); + }} /> )}
diff --git a/frontend/src/components/agents/composio/composio-tools-manager.tsx b/frontend/src/components/agents/composio/composio-tools-manager.tsx index 34370c8e..14150db6 100644 --- a/frontend/src/components/agents/composio/composio-tools-manager.tsx +++ b/frontend/src/components/agents/composio/composio-tools-manager.tsx @@ -11,6 +11,7 @@ import { Separator } from '@/components/ui/separator'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Search, Save, AlertCircle, Zap, CheckCircle2, Settings2, Loader2 } from 'lucide-react'; import { useComposioProfiles } from '@/hooks/react-query/composio/use-composio-profiles'; +import { useComposioToolkitIcon } from '@/hooks/react-query/composio/use-composio'; import { backendApi } from '@/lib/api-client'; import { toast } from 'sonner'; import { useQueryClient } from '@tanstack/react-query'; @@ -129,6 +130,9 @@ export const ComposioToolsManager: React.FC = ({ const { data: profiles } = useComposioProfiles(); const currentProfile = profileInfo || profiles?.find(p => p.profile_id === profileId); + const { data: iconData } = useComposioToolkitIcon(currentProfile?.toolkit_slug || '', { + enabled: !!currentProfile?.toolkit_slug + }); const filteredTools = useMemo(() => { if (!searchTerm) return availableTools; @@ -241,11 +245,11 @@ export const ComposioToolsManager: React.FC = ({
- {appLogo ? ( + {iconData?.icon_url || appLogo ? ( {currentProfile?.toolkit_name} ) : (