From 3063c9fea9e9328a62af8212498abed5969d1087 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:26:37 +0530 Subject: [PATCH] improve composio tools ux --- .../[projectId]/tools/components/Composio.tsx | 20 +- .../tools/components/ComposioToolsPanel.tsx | 303 +++++++++++------- .../tools/components/ToolkitCard.tsx | 218 ++++--------- 3 files changed, 268 insertions(+), 273 deletions(-) diff --git a/apps/rowboat/app/projects/[projectId]/tools/components/Composio.tsx b/apps/rowboat/app/projects/[projectId]/tools/components/Composio.tsx index dfc4f57e..dbb7c5f1 100644 --- a/apps/rowboat/app/projects/[projectId]/tools/components/Composio.tsx +++ b/apps/rowboat/app/projects/[projectId]/tools/components/Composio.tsx @@ -158,6 +158,16 @@ export function Composio() { toolkit.meta.description.toLowerCase().includes(searchLower) || toolkit.slug.toLowerCase().includes(searchLower) ); + }).sort((a, b) => { + // Sort by actual connection status first (only connected tools, not no-auth) + const aConnected = !a.no_auth && projectConfig?.composioConnectedAccounts?.[a.slug]?.status === 'ACTIVE'; + const bConnected = !b.no_auth && projectConfig?.composioConnectedAccounts?.[b.slug]?.status === 'ACTIVE'; + + if (aConnected && !bConnected) return -1; + if (!aConnected && bConnected) return 1; + + // If both have same connection status, maintain original order (don't sort alphabetically) + return 0; }); if (loading) { @@ -191,14 +201,6 @@ export function Composio() { return (
-
-
-
- -
-
-
-
@@ -276,6 +278,8 @@ export function Composio() { onClose={handleCloseToolsPanel} projectConfig={projectConfig} onUpdateToolsSelection={handleUpdateToolsSelection} + onProjectConfigUpdate={handleProjectConfigUpdate} + onRemoveToolkitTools={handleRemoveToolkitTools} isSaving={savingTools} />
diff --git a/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx b/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx index 4dd25e87..7cfb5348 100644 --- a/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx +++ b/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx @@ -2,15 +2,15 @@ import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'next/navigation'; -import { Button } from '@/components/ui/button'; import { PictureImg } from '@/components/ui/picture-img'; -import { Checkbox } from '@heroui/react'; -import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { listTools } from '@/app/actions/composio_actions'; +import { Button, Checkbox } from '@heroui/react'; +import { ChevronLeft, ChevronRight, LinkIcon, Loader2, UnlinkIcon } from 'lucide-react'; +import { listTools, deleteConnectedAccount } from '@/app/actions/composio_actions'; import { z } from 'zod'; import { ZTool, ZListResponse } from '@/app/lib/composio/composio'; import { SlidePanel } from '@/components/ui/slide-panel'; import { Project } from '@/app/lib/types/project_types'; +import { ToolkitAuthModal } from './ToolkitAuthModal'; type ToolType = z.infer; type ToolListResponse = z.infer>>; @@ -29,6 +29,8 @@ interface ComposioToolsPanelProps { onClose: () => void; projectConfig: ProjectType | null; onUpdateToolsSelection: (selectedToolObjects: ToolType[]) => void; + onProjectConfigUpdate: () => void; + onRemoveToolkitTools: (toolkitSlug: string) => void; isSaving: boolean; } @@ -38,6 +40,8 @@ export function ComposioToolsPanel({ onClose, projectConfig, onUpdateToolsSelection, + onProjectConfigUpdate, + onRemoveToolkitTools, isSaving }: ComposioToolsPanelProps) { const params = useParams(); @@ -51,6 +55,8 @@ export function ComposioToolsPanel({ const [cursorHistory, setCursorHistory] = useState([]); const [selectedTools, setSelectedTools] = useState>(new Set()); const [hasChanges, setHasChanges] = useState(false); + const [showAuthModal, setShowAuthModal] = useState(false); + const [isProcessingAuth, setIsProcessingAuth] = useState(false); const loadToolsForToolkit = useCallback(async (toolkitSlug: string, cursor: string | null = null) => { try { @@ -117,6 +123,34 @@ export function ComposioToolsPanel({ setHasChanges(false); }, [onUpdateToolsSelection, selectedTools, tools]); + const handleConnect = useCallback(() => { + setShowAuthModal(true); + }, []); + + const handleDisconnect = useCallback(async () => { + if (!toolkit) return; + + const connectedAccountId = projectConfig?.composioConnectedAccounts?.[toolkit.slug]?.id; + + setIsProcessingAuth(true); + try { + if (connectedAccountId) { + await deleteConnectedAccount(projectId, toolkit.slug, connectedAccountId); + onProjectConfigUpdate(); + onRemoveToolkitTools(toolkit.slug); + } + } catch (err: any) { + console.error('Disconnect failed:', err); + } finally { + setIsProcessingAuth(false); + } + }, [projectId, toolkit, projectConfig, onProjectConfigUpdate, onRemoveToolkitTools]); + + const handleAuthComplete = useCallback(() => { + setShowAuthModal(false); + onProjectConfigUpdate(); + }, [onProjectConfigUpdate]); + const handleClose = useCallback(() => { setTools([]); setSelectedTools(new Set()); @@ -151,119 +185,170 @@ export function ComposioToolsPanel({ const isToolkitConnected = toolkit.no_auth || projectConfig?.composioConnectedAccounts?.[toolkit.slug]?.status === 'ACTIVE'; return ( - - {toolkit.meta.logo && ( - - )} - {toolkit.name} -
- } - > -
- {/* Header */} -
-
-

Available Tools

-
- {!isToolkitConnected && !toolkit.no_auth && ( -
- Toolkit not connected -
- )} - {hasChanges && ( - - )} -
+ <> + + {toolkit.meta.logo && ( + + )} + {toolkit.name}
-
- - {/* Scrollable Tools List */} -
- {toolsLoading ? ( -
-
-

Loading tools...

-
- ) : ( -
- {tools.map((tool) => ( -
-
- handleToolSelectionChange(tool.slug, selected)} - size="sm" - isDisabled={!isToolkitConnected} - /> -
-

- {tool.name} -

-

- {tool.description} -

-
+ } + > +
+ {/* Connection Status Banner */} + {!toolkit.no_auth && ( +
+
+
+
+
+

+ {isToolkitConnected ? 'Toolkit Connected' : 'Authentication Required'} +

+

+ {isToolkitConnected + ? 'You can select and use tools from this toolkit' + : 'Connect your account to access and use tools' + } +

- ))} + +
)} -
- {/* Fixed Pagination Controls */} -
-
-
- - + {/* Header */} +
+
+

Available Tools

+
+ {hasChanges && ( + + )} +
+
+
+ + {/* Scrollable Tools List */} +
+ {toolsLoading ? ( +
+
+

Loading tools...

+
+ ) : ( +
+ {tools.map((tool) => ( +
+
+ handleToolSelectionChange(tool.slug, selected)} + size="sm" + isDisabled={!isToolkitConnected} + /> +
+

+ {tool.name} +

+

+ {tool.description} +

+
+
+
+ ))} +
+ )} +
+ + {/* Fixed Pagination Controls */} +
+
+
+ + +
-
- + + + {/* Auth Modal */} + {toolkit && ( + setShowAuthModal(false)} + toolkitSlug={toolkit.slug} + projectId={projectId} + onComplete={handleAuthComplete} + /> + )} + ); } \ No newline at end of file diff --git a/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitCard.tsx b/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitCard.tsx index 31612e3f..9fc7e278 100644 --- a/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitCard.tsx +++ b/apps/rowboat/app/projects/[projectId]/tools/components/ToolkitCard.tsx @@ -1,29 +1,28 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { Button } from '@/components/ui/button'; -import { Switch } from '@/components/ui/switch'; +import { useCallback } from 'react'; import { PictureImg } from '@/components/ui/picture-img'; -import { Wrench } from 'lucide-react'; import clsx from 'clsx'; -import { Spinner } from '@heroui/react'; -import { deleteConnectedAccount } from '@/app/actions/composio_actions'; import { z } from 'zod'; import { ZToolkit } from '@/app/lib/composio/composio'; import { Project } from '@/app/lib/types/project_types'; -import { ToolkitAuthModal } from './ToolkitAuthModal'; +import { Chip } from '@heroui/react'; +import { LinkIcon } from 'lucide-react'; type ToolkitType = z.infer; type ProjectType = z.infer; const toolkitCardStyles = { base: clsx( - "group p-6 rounded-xl transition-all duration-200", - "bg-white dark:bg-gray-900 shadow-sm dark:shadow-none", - "border-2 border-gray-200/80 dark:border-gray-700/80", - "hover:shadow-md dark:hover:shadow-none", - "hover:border-blue-200 dark:hover:border-blue-900", - "min-h-[280px] flex flex-col" + "group p-6 rounded-xl transition-all duration-200 cursor-pointer", + "bg-white dark:bg-gray-900", + "border border-gray-200 dark:border-gray-700", + "shadow-md dark:shadow-gray-900/20", + "hover:shadow-lg dark:hover:shadow-gray-900/30", + "hover:border-blue-300 dark:hover:border-blue-600", + "hover:bg-gray-50/50 dark:hover:bg-gray-800/50", + "hover:-translate-y-1", + "min-h-[200px] flex flex-col" ), }; @@ -48,45 +47,9 @@ export function ToolkitCard({ onProjectConfigUpdate, onRemoveToolkitTools }: ToolkitCardProps) { - const [isProcessing, setIsProcessing] = useState(false); - const [error, setError] = useState(null); - const [showAuthModal, setShowAuthModal] = useState(false); - - const handleToggleConnection = useCallback(async () => { - const newState = !isConnected; - - // Clear any previous error when starting a new operation - setError(null); - - if (newState) { - // Show authentication modal - setShowAuthModal(true); - } else { - // Disconnect - remove the connected account - setIsProcessing(true); - try { - if (connectedAccountId) { - await deleteConnectedAccount(projectId, toolkit.slug, connectedAccountId); - onProjectConfigUpdate(); - onRemoveToolkitTools(toolkit.slug); - } else { - // Fallback: just refresh the project config - onProjectConfigUpdate(); - } - } catch (err: any) { - console.error('Disconnect failed:', err); - const errorMessage = err.message || 'Failed to disconnect toolkit'; - setError(errorMessage); - } finally { - setIsProcessing(false); - } - } - }, [projectId, toolkit.slug, isConnected, connectedAccountId, onProjectConfigUpdate, onRemoveToolkitTools]); - - const handleAuthComplete = useCallback(() => { - // Update project config when authentication completes - onProjectConfigUpdate(); - }, [onProjectConfigUpdate]); + const handleCardClick = useCallback(() => { + onManageTools(); + }, [onManageTools]); // Calculate selected tools count for this toolkit const selectedToolsCount = projectConfig?.composioSelectedTools?.filter(tool => @@ -94,127 +57,70 @@ export function ToolkitCard({ ).length || 0; return ( -
+
-
-
- {toolkit.meta.logo && ( - - )} -
-

- {toolkit.name} -

-
- - {toolkit.meta.tools_count} tools - - {selectedToolsCount > 0 && ( - - {selectedToolsCount} selected - - )} - {toolkit.no_auth && ( - - No Auth - - )} -
+ {/* Header */} +
+ {toolkit.meta.logo && ( + + )} +
+

+ {toolkit.name} +

+
+ + {selectedToolsCount > 0 + ? `${toolkit.meta.tools_count} tools, ${selectedToolsCount} selected` + : `${toolkit.meta.tools_count} tools` + } +
-
- {toolkit.no_auth ? ( -
- {}} // No-op for no-auth toolkits - disabled={true} - className={clsx( - "data-[state=checked]:bg-emerald-500 dark:data-[state=checked]:bg-emerald-600", - "data-[state=unchecked]:bg-emerald-500 dark:data-[state=unchecked]:bg-emerald-600", - "opacity-50 cursor-not-allowed", - "scale-75" - )} - /> - - Always Available - -
- ) : ( - - )} -
+ {/* Description */}
-

+

{toolkit.meta.description}

-
+ {/* Footer */} +
-
- ID: {toolkit.slug} -
- {isProcessing && ( -
- - Processing... -
+ {isConnected && !toolkit.no_auth && ( + } + > + Connected + )} - {(isConnected || toolkit.no_auth) && !isProcessing && ( -
- {toolkit.no_auth ? 'Available' : 'Connected'} -
+ {toolkit.no_auth && ( + + Ready + )} - {error && ( -
- Error: {error} -
- )} -
- - setShowAuthModal(false)} - toolkitSlug={toolkit.slug} - projectId={projectId} - onComplete={handleAuthComplete} - />
); -} \ No newline at end of file +} \ No newline at end of file