diff --git a/apps/rowboat/app/projects/[projectId]/entities/tool_config.tsx b/apps/rowboat/app/projects/[projectId]/entities/tool_config.tsx index fd758380..1cc72df3 100644 --- a/apps/rowboat/app/projects/[projectId]/entities/tool_config.tsx +++ b/apps/rowboat/app/projects/[projectId]/entities/tool_config.tsx @@ -14,6 +14,7 @@ import { ToolParamCard } from "@/components/common/tool-param-card"; import { UserIcon, Settings, Settings2 } from "lucide-react"; import { EditableField } from "@/app/lib/components/editable-field"; import Link from "next/link"; +import { Tooltip } from "@heroui/react"; // Update textarea styles with improved states const textareaStyles = "rounded-lg p-3 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 focus:shadow-inner focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20 placeholder:text-gray-400 dark:placeholder:text-gray-500"; @@ -446,13 +447,15 @@ export function ToolConfig({ {/* Mock Section */} } - title="Mock responses" - labelWidth="md:w-64" + title={Mock responses} + labelWidth="md:w-32" className="mb-1" + singleColumnFields={true} >
-
-
+
+ +
handleUpdate({ @@ -462,16 +465,13 @@ export function ToolConfig({ size="sm" color="primary" /> - + + When enabled, this tool will be mocked. +
- - When enabled, this tool will be mocked. -
{tool.mockTool && ( -
+
Describe the response the mock tool should return. This will be shown in the chat when the tool is called. ; type ToolkitListResponse = z.infer>>; type ProjectType = z.infer; -export function Composio({ - projectId, - tools, - onAddTool -}: { +interface ComposioProps { projectId: string; tools: z.infer; onAddTool: (tool: z.infer) => void; -}) { + initialToolkitSlug?: string | null; +} + +export function Composio({ + projectId, + tools, + onAddTool, + initialToolkitSlug +}: ComposioProps) { const [toolkits, setToolkits] = useState([]); const [projectConfig, setProjectConfig] = useState(null); const [loading, setLoading] = useState(true); @@ -97,6 +101,17 @@ export function Composio({ loadAllToolkits(); }, [loadAllToolkits]); + // Auto-select toolkit if initialToolkitSlug is provided + useEffect(() => { + if (initialToolkitSlug && toolkits.length > 0) { + const toolkit = toolkits.find(t => t.slug === initialToolkitSlug); + if (toolkit) { + setSelectedToolkit(toolkit); + setIsToolsPanelOpen(true); + } + } + }, [initialToolkitSlug, toolkits]); + const filteredToolkits = toolkits.filter(toolkit => { const searchLower = searchQuery.toLowerCase(); return ( diff --git a/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx b/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx index cd0fa538..9e9aa42c 100644 --- a/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx +++ b/apps/rowboat/app/projects/[projectId]/tools/components/ComposioToolsPanel.tsx @@ -200,9 +200,6 @@ export function ComposioToolsPanel({

Select Tools

-

- Check the tools you want to add to your workflow -

{hasChanges && ( + +
))}
)} @@ -722,10 +733,14 @@ export function EntityList({ /> setShowToolsModal(false)} + onClose={() => { + setShowToolsModal(false); + setSelectedToolkitSlug(null); + }} projectId={projectId} tools={tools} onAddTool={onAddTool} + initialToolkitSlug={selectedToolkitSlug} />
); @@ -830,31 +845,48 @@ const ComposioCard = ({ projectId, workflow, onProjectToolsUpdated, -}: ComposioCardProps) => { + setSelectedToolkitSlug, + setShowToolsModal, +}: ComposioCardProps & { setSelectedToolkitSlug: (slug: string) => void, setShowToolsModal: (open: boolean) => void }) => { const [isExpanded, setIsExpanded] = useState(false); const [showAuthModal, setShowAuthModal] = useState(false); const [showDisconnectModal, setShowDisconnectModal] = useState(false); + const [showRemoveToolkitModal, setShowRemoveToolkitModal] = useState(false); const [isProcessingAuth, setIsProcessingAuth] = useState(false); + const [isProcessingRemove, setIsProcessingRemove] = useState(false); // Check if the toolkit requires authentication const hasToolkitWithAuth = card.tools.some(tool => tool.composioData && !tool.composioData.noAuth); - // Check if toolkit is connected const isToolkitConnected = !hasToolkitWithAuth || projectConfig?.composioConnectedAccounts?.[card.slug]?.status === 'ACTIVE'; - - - const handleConnect = () => { - setShowAuthModal(true); - }; - - const handleDisconnect = () => { - setShowDisconnectModal(true); + // Remove all tools from this toolkit + const handleRemoveToolkit = async () => { + setIsProcessingRemove(true); + // Disconnect if needed + if (hasToolkitWithAuth && isToolkitConnected) { + const connectedAccountId = projectConfig?.composioConnectedAccounts?.[card.slug]?.id; + try { + if (connectedAccountId) { + await deleteConnectedAccount(projectId, card.slug, connectedAccountId); + } + } catch (err) { + // ignore error, continue to remove tools + } + } + // Remove all tools from this toolkit + card.tools.forEach(tool => { + onDeleteTool(tool.name); + }); + setIsProcessingRemove(false); + setShowRemoveToolkitModal(false); + onProjectToolsUpdated?.(); }; + const handleConnect = () => setShowAuthModal(true); + const handleDisconnect = () => setShowDisconnectModal(true); const handleConfirmDisconnect = async () => { const connectedAccountId = projectConfig?.composioConnectedAccounts?.[card.slug]?.id; - setIsProcessingAuth(true); try { if (connectedAccountId) { @@ -868,13 +900,105 @@ const ComposioCard = ({ setShowDisconnectModal(false); } }; - const handleAuthComplete = () => { setShowAuthModal(false); onProjectToolsUpdated?.(); }; + // Status dot + const statusDot = ( + + + + ); + let statusPill = null; + if (!isToolkitConnected && hasToolkitWithAuth) { + statusPill = ( + + + + ); + } else if (isToolkitConnected && hasToolkitWithAuth) { + statusPill = ( + + + Connected + + ); + } + + // Always show the 3-dots menu for all toolkits + let toolkitMenu = null; + toolkitMenu = ( +
+ + + + + { + switch (key) { + case 'disconnect': + handleDisconnect && handleDisconnect(); + break; + case 'remove-toolkit': + setShowRemoveToolkitModal(true); + break; + case 'more-tools': + setSelectedToolkitSlug(card.slug); + setShowToolsModal(true); + break; + } + }} + disabledKeys={[ + ...(isProcessingAuth ? ['disconnect'] : []), + ...(isProcessingRemove ? ['remove-toolkit'] : []), + ]} + > + } + > + More tools + + {hasToolkitWithAuth && isToolkitConnected ? ( +
+ ) : ( + + )} + > + {isProcessingAuth ? 'Disconnecting...' : 'Disconnect'} + + ) : null} +
+ ) : ( + + )} + > + {isProcessingRemove ? 'Removing...' : 'Remove Toolkit'} + + + +
+ ); return ( <> @@ -882,7 +1006,7 @@ const ComposioCard = ({
- - {/* Status Badge - only show orange when requires auth and not connected */} - {hasToolkitWithAuth && !isToolkitConnected && ( - -
- ○ -
-
- )} - - {/* Actions Dropdown - only show when requires auth */} - {hasToolkitWithAuth && ( -
- - - - - { - switch (key) { - case 'connect': - handleConnect(); - break; - case 'disconnect': - handleDisconnect(); - break; - } - }} - disabledKeys={[ - ...(isProcessingAuth ? ['connect', 'disconnect'] : []), - ...(hasToolkitWithAuth && isToolkitConnected ? [] : ['disconnect']), - ...(hasToolkitWithAuth && !isToolkitConnected ? [] : ['connect']) - ]} - > - - -
- ) : ( - - ) - } - > - {isProcessingAuth ? 'Disconnecting...' : 'Disconnect'} - - -
- ) : ( - - ) - } - > - {isProcessingAuth ? 'Connecting...' : 'Connect'} - - - -
- )} +
{toolkitMenu}
- {isExpanded && ( -
- {card.tools.map((tool, index) => ( -
- onSelectTool(tool.name)} - disabled={tool.isLibrary} - selectedRef={selectedEntity?.type === "tool" && selectedEntity.name === tool.name ? selectedRef : undefined} - icon={ -
- } - isMocked={tool.mockTool} - menuContent={ -
- + {card.tools.map((tool, index) => ( +
+ {/* Toolkit icon or fallback */} + {card.logo ? ( +
+
- } - /> -
- ))} -
- )} + ) : ( + + )} + + {tool.mockTool && ( + Mocked + )} + + + +
+ ))} + {/* More tools option */} + +
+ )} - {/* Auth Modal */} {hasToolkitWithAuth && ( )} - {/* Disconnect Confirmation Modal */} + {/* Remove Toolkit Confirmation Modal */} + setShowRemoveToolkitModal(false)} + onConfirm={handleRemoveToolkit} + title={`Remove ${card.name} Toolkit`} + confirmationQuestion={`Are you sure you want to remove the ${card.name} toolkit and all its tools? This will disconnect and delete all tools from this toolkit.`} + confirmButtonText="Remove Toolkit" + isLoading={isProcessingRemove} + /> ); }; diff --git a/apps/rowboat/components/common/section-card.tsx b/apps/rowboat/components/common/section-card.tsx index 90d7be32..19878402 100644 --- a/apps/rowboat/components/common/section-card.tsx +++ b/apps/rowboat/components/common/section-card.tsx @@ -9,6 +9,11 @@ interface SectionCardProps { className?: string; style?: React.CSSProperties; chevronSize?: string; + /** + * If true, all fields are single column. If string[], only those fields are single column (by label). + * If not provided, all fields use the default two-column layout. + */ + singleColumnFields?: string[] | boolean; } export function SectionCard({ icon, title, children, labelWidth = 'md:w-32', className = '', style, chevronSize = 'w-4 h-4' }: SectionCardProps) {