mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-25 18:55:19 +02:00
refactor tools UX: part 1
This commit is contained in:
parent
cccd383b92
commit
751a86c34d
21 changed files with 1462 additions and 258 deletions
|
|
@ -5,7 +5,7 @@ import { useParams } from 'next/navigation';
|
|||
import { Button } from '@/components/ui/button';
|
||||
import { Info, RefreshCw, Search } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { listToolkits, listTools, updateComposioSelectedTools } from '@/app/actions/composio_actions';
|
||||
import { listToolkits, listTools, updateComposioSelectedTools, getComposioToolsFromWorkflow } from '@/app/actions/composio_actions';
|
||||
import { getProjectConfig } from '@/app/actions/project_actions';
|
||||
import { z } from 'zod';
|
||||
import { ZToolkit, ZListResponse, ZTool } from '@/app/lib/composio/composio';
|
||||
|
|
@ -30,6 +30,7 @@ export function Composio() {
|
|||
const [selectedToolkit, setSelectedToolkit] = useState<ToolkitType | null>(null);
|
||||
const [isToolsPanelOpen, setIsToolsPanelOpen] = useState(false);
|
||||
const [savingTools, setSavingTools] = useState(false);
|
||||
const [composioSelectedTools, setComposioSelectedTools] = useState<z.infer<typeof ZTool>[]>([]);
|
||||
|
||||
const loadProjectConfig = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -41,6 +42,15 @@ export function Composio() {
|
|||
}
|
||||
}, [projectId]);
|
||||
|
||||
const loadComposioSelectedTools = useCallback(async () => {
|
||||
try {
|
||||
const tools = await getComposioToolsFromWorkflow(projectId);
|
||||
setComposioSelectedTools(tools);
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching composio selected tools:', err);
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
const loadAllToolkits = useCallback(async () => {
|
||||
let cursor: string | null = null;
|
||||
let allToolkits: ToolkitType[] = [];
|
||||
|
|
@ -87,15 +97,16 @@ export function Composio() {
|
|||
|
||||
const handleProjectConfigUpdate = useCallback(() => {
|
||||
loadProjectConfig();
|
||||
}, [loadProjectConfig]);
|
||||
loadComposioSelectedTools();
|
||||
}, [loadProjectConfig, loadComposioSelectedTools]);
|
||||
|
||||
const handleUpdateToolsSelection = useCallback(async (selectedToolObjects: z.infer<typeof ZTool>[]) => {
|
||||
if (!projectId) return;
|
||||
|
||||
setSavingTools(true);
|
||||
try {
|
||||
// Get existing selected tools from project config
|
||||
const existingSelectedTools = projectConfig?.composioSelectedTools || [];
|
||||
// Get existing selected tools from workflow
|
||||
const existingSelectedTools = composioSelectedTools;
|
||||
|
||||
// Create a map of existing tools by slug for easy lookup
|
||||
const existingToolsMap = new Map(existingSelectedTools.map(tool => [tool.slug, tool]));
|
||||
|
|
@ -110,22 +121,22 @@ export function Composio() {
|
|||
|
||||
await updateComposioSelectedTools(projectId, mergedSelectedTools);
|
||||
|
||||
// Refresh project config to get updated data
|
||||
await loadProjectConfig();
|
||||
// Refresh data to get updated tools
|
||||
await loadComposioSelectedTools();
|
||||
} catch (error) {
|
||||
console.error('Error saving tool selection:', error);
|
||||
} finally {
|
||||
setSavingTools(false);
|
||||
}
|
||||
}, [projectId, projectConfig, loadProjectConfig]);
|
||||
}, [projectId, composioSelectedTools, loadComposioSelectedTools]);
|
||||
|
||||
const handleRemoveToolkitTools = useCallback(async (toolkitSlug: string) => {
|
||||
if (!projectId) return;
|
||||
|
||||
setSavingTools(true);
|
||||
try {
|
||||
// Get existing selected tools from project config
|
||||
const existingSelectedTools = projectConfig?.composioSelectedTools || [];
|
||||
// Get existing selected tools from workflow
|
||||
const existingSelectedTools = composioSelectedTools;
|
||||
|
||||
// Filter out all tools from the specified toolkit
|
||||
const filteredSelectedTools = existingSelectedTools.filter(tool =>
|
||||
|
|
@ -134,18 +145,19 @@ export function Composio() {
|
|||
|
||||
await updateComposioSelectedTools(projectId, filteredSelectedTools);
|
||||
|
||||
// Refresh project config to get updated data
|
||||
await loadProjectConfig();
|
||||
// Refresh data to get updated tools
|
||||
await loadComposioSelectedTools();
|
||||
} catch (error) {
|
||||
console.error('Error removing toolkit tools:', error);
|
||||
} finally {
|
||||
setSavingTools(false);
|
||||
}
|
||||
}, [projectId, projectConfig, loadProjectConfig]);
|
||||
}, [projectId, composioSelectedTools, loadComposioSelectedTools]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProjectConfig();
|
||||
}, [loadProjectConfig]);
|
||||
loadComposioSelectedTools();
|
||||
}, [loadProjectConfig, loadComposioSelectedTools]);
|
||||
|
||||
useEffect(() => {
|
||||
loadAllToolkits();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useParams } from 'next/navigation';
|
|||
import { PictureImg } from '@/components/ui/picture-img';
|
||||
import { Button, Checkbox } from '@heroui/react';
|
||||
import { ChevronLeft, ChevronRight, LinkIcon, Loader2, UnlinkIcon } from 'lucide-react';
|
||||
import { listTools, deleteConnectedAccount } from '@/app/actions/composio_actions';
|
||||
import { listTools, deleteConnectedAccount, getComposioToolsFromWorkflow } from '@/app/actions/composio_actions';
|
||||
import { z } from 'zod';
|
||||
import { ZTool, ZListResponse } from '@/app/lib/composio/composio';
|
||||
import { SlidePanel } from '@/components/ui/slide-panel';
|
||||
|
|
@ -57,6 +57,7 @@ export function ComposioToolsPanel({
|
|||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||
const [isProcessingAuth, setIsProcessingAuth] = useState(false);
|
||||
const [composioSelectedTools, setComposioSelectedTools] = useState<ToolType[]>([]);
|
||||
|
||||
const loadToolsForToolkit = useCallback(async (toolkitSlug: string, cursor: string | null = null) => {
|
||||
try {
|
||||
|
|
@ -80,6 +81,15 @@ export function ComposioToolsPanel({
|
|||
}
|
||||
}, [projectId]);
|
||||
|
||||
const loadComposioSelectedTools = useCallback(async () => {
|
||||
try {
|
||||
const tools = await getComposioToolsFromWorkflow(projectId);
|
||||
setComposioSelectedTools(tools);
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching composio selected tools:', err);
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
const handleNextPage = useCallback(async () => {
|
||||
if (!nextCursor || !toolkit) return;
|
||||
|
||||
|
|
@ -164,14 +174,21 @@ export function ComposioToolsPanel({
|
|||
}
|
||||
}, [onClose, hasChanges]);
|
||||
|
||||
// Initialize selected tools from project config when opening the panel
|
||||
// Initialize selected tools from workflow when opening the panel
|
||||
useEffect(() => {
|
||||
if (toolkit && isOpen && projectConfig?.composioSelectedTools) {
|
||||
const toolSlugs = new Set(projectConfig.composioSelectedTools.map(tool => tool.slug));
|
||||
if (toolkit && isOpen) {
|
||||
loadComposioSelectedTools();
|
||||
}
|
||||
}, [toolkit, isOpen, loadComposioSelectedTools]);
|
||||
|
||||
// Set selected tools when composioSelectedTools is loaded
|
||||
useEffect(() => {
|
||||
if (toolkit && composioSelectedTools.length > 0) {
|
||||
const toolSlugs = new Set(composioSelectedTools.map(tool => tool.slug));
|
||||
setSelectedTools(toolSlugs);
|
||||
setHasChanges(false);
|
||||
}
|
||||
}, [toolkit, isOpen, projectConfig]);
|
||||
}, [toolkit, composioSelectedTools]);
|
||||
|
||||
useEffect(() => {
|
||||
if (toolkit && isOpen) {
|
||||
|
|
@ -210,44 +227,46 @@ export function ComposioToolsPanel({
|
|||
<div className={`mb-6 p-4 rounded-lg border-2 ${
|
||||
isToolkitConnected
|
||||
? 'bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800'
|
||||
: 'bg-orange-50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800'
|
||||
: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'
|
||||
}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-3 h-3 rounded-full ${
|
||||
isToolkitConnected ? 'bg-emerald-500' : 'bg-orange-500'
|
||||
isToolkitConnected ? 'bg-emerald-500' : 'bg-blue-500'
|
||||
}`}></div>
|
||||
<div>
|
||||
<h3 className={`font-semibold text-sm ${
|
||||
isToolkitConnected
|
||||
? 'text-emerald-800 dark:text-emerald-200'
|
||||
: 'text-orange-800 dark:text-orange-200'
|
||||
: 'text-blue-800 dark:text-blue-200'
|
||||
}`}>
|
||||
{isToolkitConnected ? 'Toolkit Connected' : 'Authentication Required'}
|
||||
</h3>
|
||||
<p className={`text-xs mt-0.5 ${
|
||||
isToolkitConnected
|
||||
? 'text-emerald-700 dark:text-emerald-300'
|
||||
: 'text-orange-700 dark:text-orange-300'
|
||||
: 'text-blue-700 dark:text-blue-300'
|
||||
}`}>
|
||||
{isToolkitConnected
|
||||
? 'You can select and use tools from this toolkit'
|
||||
: 'Connect your account to access and use tools'
|
||||
: 'You can select tools now. Authentication will be required in the build view to use them.'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onPress={isToolkitConnected ? handleDisconnect : handleConnect}
|
||||
disabled={isProcessingAuth}
|
||||
color={isToolkitConnected ? "danger" : "primary"}
|
||||
isLoading={isProcessingAuth}
|
||||
startContent={isToolkitConnected ? <UnlinkIcon className="h-4 w-4" /> : <LinkIcon className="h-4 w-4" />}
|
||||
>
|
||||
{isToolkitConnected ? 'Disconnect' : 'Connect Now'}
|
||||
</Button>
|
||||
{isToolkitConnected && (
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onPress={handleDisconnect}
|
||||
disabled={isProcessingAuth}
|
||||
color="danger"
|
||||
isLoading={isProcessingAuth}
|
||||
startContent={<UnlinkIcon className="h-4 w-4" />}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -263,7 +282,7 @@ export function ComposioToolsPanel({
|
|||
size="sm"
|
||||
color="primary"
|
||||
onPress={handleSaveTools}
|
||||
disabled={isSaving || !isToolkitConnected}
|
||||
disabled={isSaving}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
Save Changes
|
||||
|
|
@ -283,17 +302,12 @@ export function ComposioToolsPanel({
|
|||
) : (
|
||||
<div className="space-y-4">
|
||||
{tools.map((tool) => (
|
||||
<div key={tool.slug} className={`group p-4 rounded-lg transition-all duration-200 border border-transparent ${
|
||||
isToolkitConnected
|
||||
? 'bg-gray-50/50 dark:bg-gray-800/50 hover:bg-gray-100/50 dark:hover:bg-gray-700/50 hover:border-gray-200 dark:hover:border-gray-600'
|
||||
: 'bg-gray-100/50 dark:bg-gray-900/50 opacity-60'
|
||||
}`}>
|
||||
<div key={tool.slug} className="group p-4 rounded-lg transition-all duration-200 border border-transparent bg-gray-50/50 dark:bg-gray-800/50 hover:bg-gray-100/50 dark:hover:bg-gray-700/50 hover:border-gray-200 dark:hover:border-gray-600">
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
isSelected={selectedTools.has(tool.slug)}
|
||||
onValueChange={(selected) => handleToolSelectionChange(tool.slug, selected)}
|
||||
size="sm"
|
||||
isDisabled={!isToolkitConnected}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-1">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { Modal } from '@/components/ui/modal';
|
|||
type McpServerType = z.infer<typeof MCPServer>;
|
||||
type McpToolType = z.infer<typeof MCPServer>['tools'][number];
|
||||
|
||||
export function CustomServers() {
|
||||
export function CustomServers({ onToolsUpdated }: { onToolsUpdated?: () => void }) {
|
||||
const params = useParams();
|
||||
const projectId = typeof params.projectId === 'string' ? params.projectId : params.projectId?.[0];
|
||||
if (!projectId) throw new Error('Project ID is required');
|
||||
|
|
@ -92,6 +92,9 @@ export function CustomServers() {
|
|||
return s;
|
||||
});
|
||||
});
|
||||
|
||||
// Notify parent component about tool updates
|
||||
onToolsUpdated?.();
|
||||
} catch (err) {
|
||||
console.error('Toggle failed:', { server: server.name, error: err });
|
||||
} finally {
|
||||
|
|
@ -161,6 +164,9 @@ export function CustomServers() {
|
|||
// Update selectedTools to include all tools for the custom server
|
||||
setSelectedTools(new Set(updatedAvailableTools.map(tool => tool.id)));
|
||||
}
|
||||
|
||||
// Notify parent component about tool updates
|
||||
onToolsUpdated?.();
|
||||
} finally {
|
||||
setSyncingServers(prev => {
|
||||
const next = new Set(prev);
|
||||
|
|
@ -207,6 +213,9 @@ export function CustomServers() {
|
|||
|
||||
// Fetch tools for the new server using the formatted URL
|
||||
await handleSyncServer(formattedServer);
|
||||
|
||||
// Notify parent component about tool updates
|
||||
onToolsUpdated?.();
|
||||
} catch (err) {
|
||||
console.error('Error adding server:', err);
|
||||
setError('Failed to add server. Please try again.');
|
||||
|
|
@ -229,6 +238,9 @@ export function CustomServers() {
|
|||
if (selectedServer?.name === server.name) {
|
||||
setSelectedServer(null);
|
||||
}
|
||||
|
||||
// Notify parent component about tool updates
|
||||
onToolsUpdated?.();
|
||||
} catch (err) {
|
||||
console.error('Error removing server:', err);
|
||||
setError('Failed to remove server. Please try again.');
|
||||
|
|
@ -271,6 +283,9 @@ export function CustomServers() {
|
|||
});
|
||||
|
||||
setHasToolChanges(false);
|
||||
|
||||
// Notify parent component about tool updates
|
||||
onToolsUpdated?.();
|
||||
} catch (error) {
|
||||
console.error('Error saving tool selection:', error);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -52,9 +52,8 @@ export function ToolkitCard({
|
|||
}, [onManageTools]);
|
||||
|
||||
// Calculate selected tools count for this toolkit
|
||||
const selectedToolsCount = projectConfig?.composioSelectedTools?.filter(tool =>
|
||||
tool.toolkit.slug === toolkit.slug
|
||||
).length || 0;
|
||||
// TODO: Update to use workflow-based tools count
|
||||
const selectedToolsCount = 0;
|
||||
|
||||
return (
|
||||
<div className={toolkitCardStyles.base} onClick={handleCardClick}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue