mirror of https://github.com/kortix-ai/suna.git
standardise ui
This commit is contained in:
parent
6796216a4f
commit
4d1e11267f
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Search } from 'lucide-react';
|
||||
import { AGENTPRESS_TOOL_DEFINITIONS, getToolDisplayName } from './tools';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
|
@ -59,49 +60,57 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange, disabled = false
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex-shrink-0 mb-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{getSelectedToolsCount()} selected
|
||||
</span>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Default Tools</h3>
|
||||
<p className="text-xs text-muted-foreground">Configure default agentpress tools • {getSelectedToolsCount()} selected</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="gap-4 grid grid-cols-1 md:grid-cols-2">
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
{getFilteredTools().map(([toolName, toolInfo]) => (
|
||||
<div
|
||||
key={toolName}
|
||||
className="flex items-center gap-3 p-3 bg-muted/50 rounded-lg border hover:border-border/80 transition-colors"
|
||||
className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className={`w-10 h-10 rounded-lg ${toolInfo.color} flex items-center justify-center flex-shrink-0`}>
|
||||
<span className="text-lg">{toolInfo.icon}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<h4 className="font-medium text-sm">
|
||||
{getToolDisplayName(toolName)}
|
||||
</h4>
|
||||
<Switch
|
||||
checked={isToolEnabled(tools[toolName])}
|
||||
onCheckedChange={(checked) => handleToolToggle(toolName, checked)}
|
||||
className="flex-shrink-0"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className="flex items-center space-x-4 flex-1">
|
||||
<div className={`w-10 h-10 rounded-lg ${toolInfo.color} border flex items-center justify-center flex-shrink-0`}>
|
||||
<span className="text-lg">{toolInfo.icon}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
{toolInfo.description}
|
||||
</p>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h4 className="text-sm font-medium truncate">
|
||||
{getToolDisplayName(toolName)}
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
{toolInfo.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center flex-shrink-0">
|
||||
<Switch
|
||||
checked={isToolEnabled(tools[toolName])}
|
||||
onCheckedChange={(checked) => handleToolToggle(toolName, checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{getFilteredTools().length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-4xl mb-3">🔍</div>
|
||||
<h3 className="text-sm font-medium mb-1">No tools found</h3>
|
||||
<p className="text-xs text-muted-foreground">Try adjusting your search criteria</p>
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Search className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-2">
|
||||
No tools found
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Try adjusting your search criteria
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -393,7 +393,6 @@ export function ConfigurationTab({
|
|||
<div className="bg-muted rounded-xl h-10 w-10 flex items-center justify-center transition-all duration-300 group-hover:scale-105">
|
||||
<Zap className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="text-left flex-1">
|
||||
<h4 className="text-sm font-semibold text-foreground mb-1 group-hover:text-primary transition-colors duration-300">Triggers</h4>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||
import { Badge } from '@/components/ui/badge';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Plus,
|
||||
Edit2,
|
||||
|
@ -621,7 +621,7 @@ export const AgentKnowledgeBaseManager = ({ agentId, agentName }: AgentKnowledge
|
|||
|
||||
return (
|
||||
<div
|
||||
className="space-y-6"
|
||||
className="space-y-4"
|
||||
onDragEnter={handleDrag}
|
||||
onDragLeave={handleDrag}
|
||||
onDragOver={handleDrag}
|
||||
|
@ -639,27 +639,33 @@ export const AgentKnowledgeBaseManager = ({ agentId, agentName }: AgentKnowledge
|
|||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search knowledge entries..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Knowledge Base</h3>
|
||||
<p className="text-xs text-muted-foreground">Upload and manage knowledge for the agent</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 h-9 w-48"
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={() => handleOpenAddDialog()} size="sm" className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Knowledge
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={() => handleOpenAddDialog()} className="gap-2 ml-4">
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Knowledge
|
||||
</Button>
|
||||
</div>
|
||||
{entries.length === 0 ? (
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Bot className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold mb-2">No Agent Knowledge Entries</h3>
|
||||
<p className="text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
<h4 className="text-sm font-semibold text-foreground mb-2">No knowledge entries</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Add knowledge entries to provide <span className="font-medium">{agentName}</span> with specialized context,
|
||||
guidelines, and information it should always remember.
|
||||
</p>
|
||||
|
@ -667,9 +673,16 @@ export const AgentKnowledgeBaseManager = ({ agentId, agentName }: AgentKnowledge
|
|||
) : (
|
||||
<div className="space-y-3">
|
||||
{filteredEntries.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Search className="h-8 w-8 mx-auto text-muted-foreground/50 mb-2" />
|
||||
<p className="text-sm text-muted-foreground">No entries match your search</p>
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Search className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-2">
|
||||
No entries match your search
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Try adjusting your search criteria or add new knowledge entries
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredEntries.map((entry) => {
|
||||
|
@ -678,106 +691,70 @@ export const AgentKnowledgeBaseManager = ({ agentId, agentName }: AgentKnowledge
|
|||
const SourceIcon = getSourceIcon(entry.source_type || 'manual', entry.source_metadata?.filename);
|
||||
|
||||
return (
|
||||
<Card
|
||||
<div
|
||||
key={entry.entry_id}
|
||||
className={cn(
|
||||
"group transition-all p-0",
|
||||
entry.is_active
|
||||
? "bg-card"
|
||||
: "bg-muted/30 opacity-70"
|
||||
)}
|
||||
className="flex items-start justify-between p-4 rounded-lg border bg-card hover:bg-muted/50 transition-colors group"
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<SourceIcon className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<h3 className="font-medium truncate">{entry.name}</h3>
|
||||
{!entry.is_active && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<EyeOff className="h-3 w-3 mr-1" />
|
||||
Disabled
|
||||
</Badge>
|
||||
)}
|
||||
{entry.source_type && entry.source_type !== 'manual' && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{entry.source_type === 'git_repo' ? 'Git' :
|
||||
entry.source_type === 'zip_extracted' ? 'ZIP' : 'File'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{entry.description && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-1">
|
||||
{entry.description}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-foreground/80 line-clamp-2 leading-relaxed">
|
||||
{entry.content}
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className={cn("text-xs gap-1", contextConfig.color)}>
|
||||
<ContextIcon className="h-3 w-3" />
|
||||
{contextConfig.label}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{new Date(entry.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
{entry.file_size && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(entry.file_size / 1024).toFixed(1)}KB
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{entry.content_tokens && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
~{entry.content_tokens.toLocaleString()} tokens
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-36">
|
||||
<DropdownMenuItem onClick={() => handleOpenEditDialog(entry)}>
|
||||
<Edit2 className="h-4 w-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleToggleActive(entry)}>
|
||||
{entry.is_active ? (
|
||||
<>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
Disable
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Eye className="h-4 w-4" />
|
||||
Enable
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => setDeleteEntryId(entry.entry_id)}
|
||||
className="text-destructive focus:bg-destructive/10 focus:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="flex-1 min-w-0 space-y-2">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h4 className="text-sm font-medium truncate">{entry.name}</h4>
|
||||
{entry.source_type && entry.source_type !== 'manual' && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{entry.source_type === 'git_repo' ? 'Git' :
|
||||
entry.source_type === 'zip_extracted' ? 'ZIP' : 'File'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{entry.description && (
|
||||
<p className="text-xs text-muted-foreground line-clamp-1">
|
||||
{entry.description}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-foreground/80 line-clamp-2 leading-relaxed">
|
||||
{entry.content}
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className={cn("text-xs gap-1", contextConfig.color)}>
|
||||
<ContextIcon className="h-3 w-3" />
|
||||
{contextConfig.label}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{new Date(entry.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
{entry.file_size && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(entry.file_size / 1024).toFixed(1)}KB
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{entry.content_tokens && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
~{entry.content_tokens.toLocaleString()} tokens
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => handleOpenEditDialog(entry)}
|
||||
>
|
||||
<Edit2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
onClick={() => setDeleteEntryId(entry.entry_id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
|
|
@ -2,7 +2,17 @@ import React from 'react';
|
|||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Settings, X, Sparkles, Key, AlertTriangle } from 'lucide-react';
|
||||
import { Settings, X, Sparkles, Key, AlertTriangle, Trash2 } from 'lucide-react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { MCPConfiguration } from './types';
|
||||
import { useCredentialProfilesForMcp } from '@/hooks/react-query/mcp/use-credential-profiles';
|
||||
|
||||
|
@ -50,7 +60,7 @@ const MCPLogo: React.FC<{ mcp: MCPConfiguration }> = ({ mcp }) => {
|
|||
const firstLetter = mcp.name.charAt(0).toUpperCase();
|
||||
|
||||
return (
|
||||
<div className="w-6 h-6 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||
<div className="w-4 h-4 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||
{logoUrl ? (
|
||||
<img
|
||||
src={logoUrl}
|
||||
|
@ -87,49 +97,51 @@ const MCPConfigurationItem: React.FC<{
|
|||
const hasCredentialProfile = !!profileId && !!selectedProfile;
|
||||
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-muted/50 transition-colors">
|
||||
<div className="flex items-center space-x-4 flex-1">
|
||||
<div className="p-2 rounded-lg bg-muted border">
|
||||
<MCPLogo mcp={mcp} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<div className="font-medium text-sm truncate">{mcp.name}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<span>{mcp.enabledTools?.length || 0} tools enabled</span>
|
||||
{hasCredentialProfile && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Key className="h-3 w-3 text-green-600" />
|
||||
<span className="text-green-600 font-medium truncate max-w-24">
|
||||
{selectedProfile.profile_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h4 className="text-sm font-medium truncate">{mcp.name}</h4>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<span>{mcp.enabledTools?.length || 0} tools enabled</span>
|
||||
{hasCredentialProfile && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Key className="h-3 w-3 text-green-600" />
|
||||
<span className="text-green-600 font-medium truncate max-w-24">
|
||||
{selectedProfile.profile_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{onConfigureTools && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onConfigureTools(index)}
|
||||
title="Configure tools"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||
{onConfigureTools && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onRemove(index)}
|
||||
title="Remove integration"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => onConfigureTools(index)}
|
||||
title="Configure tools"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
onClick={() => onRemove(index)}
|
||||
title="Remove integration"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -139,20 +151,58 @@ export const ConfiguredMcpList: React.FC<ConfiguredMcpListProps> = ({
|
|||
onRemove,
|
||||
onConfigureTools,
|
||||
}) => {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
|
||||
const [mcpToDelete, setMcpToDelete] = React.useState<{ mcp: MCPConfiguration; index: number } | null>(null);
|
||||
|
||||
const handleDeleteClick = (mcp: MCPConfiguration, index: number) => {
|
||||
setMcpToDelete({ mcp, index });
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (mcpToDelete) {
|
||||
onRemove(mcpToDelete.index);
|
||||
setMcpToDelete(null);
|
||||
setDeleteDialogOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (configuredMCPs.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{configuredMCPs.map((mcp, index) => (
|
||||
<MCPConfigurationItem
|
||||
key={index}
|
||||
mcp={mcp}
|
||||
index={index}
|
||||
onEdit={onEdit}
|
||||
onRemove={onRemove}
|
||||
onConfigureTools={onConfigureTools}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
{configuredMCPs.map((mcp, index) => (
|
||||
<MCPConfigurationItem
|
||||
key={index}
|
||||
mcp={mcp}
|
||||
index={index}
|
||||
onEdit={onEdit}
|
||||
onRemove={(idx) => handleDeleteClick(mcp, idx)}
|
||||
onConfigureTools={onConfigureTools}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Remove Integration</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to remove the "{mcpToDelete?.mcp.name}" integration? This will disconnect all associated tools and cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-destructive hover:bg-destructive/90"
|
||||
>
|
||||
Remove Integration
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -110,56 +110,46 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{configuredMCPs.length === 0 && (
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4">
|
||||
<Zap className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">
|
||||
No integrations configured
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Browse the app registry to connect your apps through Composio or add custom MCP servers
|
||||
</p>
|
||||
<div className="flex gap-2 justify-center">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} variant="default">
|
||||
<Store className="h-4 w-4" />
|
||||
Browse Apps
|
||||
</Button>
|
||||
<Button onClick={() => setShowCustomDialog(true)} variant="outline">
|
||||
<Server className="h-4 w-4" />
|
||||
Custom MCP
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configuredMCPs.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<ConfiguredMcpList
|
||||
configuredMCPs={configuredMCPs}
|
||||
onEdit={handleEditMCP}
|
||||
onRemove={handleRemoveMCP}
|
||||
onConfigureTools={handleConfigureTools}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Integrations</h3>
|
||||
<p className="text-xs text-muted-foreground">Connect external services via MCPs</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} size="sm" variant="default" className="gap-2">
|
||||
<Store className="h-4 w-4" />
|
||||
Browse Apps
|
||||
</Button>
|
||||
<Button onClick={() => setShowCustomDialog(true)} size="sm" variant="outline" className="gap-2">
|
||||
<Server className="h-4 w-4" />
|
||||
Custom MCP
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{configuredMCPs.length > 0 && (
|
||||
<div className="flex-shrink-0 pt-4">
|
||||
<div className="flex gap-2 justify-center">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} variant="default">
|
||||
<Store className="h-4 w-4" />
|
||||
Browse Apps
|
||||
</Button>
|
||||
<Button onClick={() => setShowCustomDialog(true)} variant="outline">
|
||||
<Server className="h-4 w-4" />
|
||||
Custom MCP
|
||||
</Button>
|
||||
{configuredMCPs.length === 0 && (
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Server className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-2">
|
||||
No integrations configured
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Browse the app registry to connect your apps through Composio or add custom MCP servers
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configuredMCPs.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<ConfiguredMcpList
|
||||
configuredMCPs={configuredMCPs}
|
||||
onEdit={handleEditMCP}
|
||||
onRemove={handleRemoveMCP}
|
||||
onConfigureTools={handleConfigureTools}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -59,47 +59,59 @@ export function AgentPlaybooksConfiguration({ agentId, agentName }: AgentPlayboo
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">Playbooks</div>
|
||||
<div className="text-xs text-muted-foreground">Lightweight, variable-driven instructions stored via workflows</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Playbooks</h3>
|
||||
<p className="text-xs text-muted-foreground">Simple variable-driven runs</p>
|
||||
</div>
|
||||
<Button onClick={() => { setEditing(null); setIsCreateOpen(true); }}>
|
||||
<Plus className="h-4 w-4 mr-1" /> New Playbook
|
||||
<Button onClick={() => { setEditing(null); setIsCreateOpen(true); }} size="sm" className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
New Playbook
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="text-sm text-muted-foreground">Loading...</div>
|
||||
) : playbooks.length === 0 ? (
|
||||
<Card className="p-6 text-center text-sm text-muted-foreground shadow-none">No playbooks yet.</Card>
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<BookOpen className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground mb-2">
|
||||
No playbooks yet
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Create your first playbook to automate common workflows with variable-driven runs
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="space-y-2">
|
||||
{playbooks.map((pb) => (
|
||||
<Card key={pb.id} className="w-full p-4 shadow-none">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium truncate">{pb.name}</div>
|
||||
{pb.description ? (
|
||||
<div className="text-xs text-muted-foreground truncate">{pb.description}</div>
|
||||
) : null}
|
||||
<div key={pb.id} className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-muted/50 transition-colors group">
|
||||
<div className="flex items-center space-x-4 flex-1">
|
||||
<div className="p-2 rounded-lg bg-muted border">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
|
||||
<Button size="icon" variant="outline" className="h-8 w-8" onClick={() => { setEditing(pb); setIsCreateOpen(true); }} aria-label="Edit playbook">
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" variant="destructive" className="h-8 w-8" onClick={() => handleDelete(pb)} aria-label="Delete playbook">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" className="h-8 w-8" onClick={() => setExecuting(pb)} aria-label="Run playbook">
|
||||
<Play className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h4 className="text-sm font-medium truncate">{pb.name}</h4>
|
||||
</div>
|
||||
{pb.description && (
|
||||
<p className="text-xs text-muted-foreground truncate">{pb.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex items-center space-x-2 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0">
|
||||
<Button size="sm" variant="ghost" className="h-8 w-8 p-0" onClick={() => setExecuting(pb)} aria-label="Run playbook">
|
||||
<Play className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" className="h-8 w-8 p-0" onClick={() => { setEditing(pb); setIsCreateOpen(true); }} aria-label="Edit playbook">
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" className="h-8 w-8 p-0 text-destructive hover:text-destructive" onClick={() => handleDelete(pb)} aria-label="Delete playbook">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -127,35 +127,38 @@ export const AgentTriggersConfiguration: React.FC<AgentTriggersConfigurationProp
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex-1 overflow-y-auto space-y-4">
|
||||
<OneClickIntegrations agentId={agentId} />
|
||||
|
||||
{triggers.length > 0 && (
|
||||
<ConfiguredTriggersList
|
||||
triggers={triggers}
|
||||
onEdit={handleEditTrigger}
|
||||
onRemove={handleRemoveTrigger}
|
||||
onToggle={handleToggleTrigger}
|
||||
isLoading={deleteTriggerMutation.isPending || toggleTriggerMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoading && triggers.length === 0 && (
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Zap className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground">
|
||||
No triggers configured
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Click on a trigger provider above to get started
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-foreground">Triggers</h3>
|
||||
<p className="text-xs text-muted-foreground mb-4">Set up automated agent runs</p>
|
||||
</div>
|
||||
|
||||
<OneClickIntegrations agentId={agentId} />
|
||||
|
||||
{triggers.length > 0 && (
|
||||
<ConfiguredTriggersList
|
||||
triggers={triggers}
|
||||
onEdit={handleEditTrigger}
|
||||
onRemove={handleRemoveTrigger}
|
||||
onToggle={handleToggleTrigger}
|
||||
isLoading={deleteTriggerMutation.isPending || toggleTriggerMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoading && triggers.length === 0 && (
|
||||
<div className="text-center py-12 px-6 bg-muted/30 rounded-xl border-2 border-dashed border-border">
|
||||
<div className="mx-auto w-12 h-12 bg-muted rounded-full flex items-center justify-center mb-4 border">
|
||||
<Zap className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-semibold text-foreground">
|
||||
No triggers configured
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground mb-6 max-w-sm mx-auto">
|
||||
Click on a trigger provider above to get started
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configuringProvider && (
|
||||
<Dialog open={!!configuringProvider} onOpenChange={() => setConfiguringProvider(null)}>
|
||||
<TriggerConfigDialog
|
||||
|
|
|
@ -19,6 +19,16 @@ import {
|
|||
} from 'lucide-react';
|
||||
import { TriggerConfiguration } from './types';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { getTriggerIcon } from './utils';
|
||||
import { truncateString } from '@/lib/utils';
|
||||
|
||||
|
@ -65,6 +75,22 @@ export const ConfiguredTriggersList: React.FC<ConfiguredTriggersListProps> = ({
|
|||
onToggle,
|
||||
isLoading = false,
|
||||
}) => {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
|
||||
const [triggerToDelete, setTriggerToDelete] = React.useState<TriggerConfiguration | null>(null);
|
||||
|
||||
const handleDeleteClick = (trigger: TriggerConfiguration) => {
|
||||
setTriggerToDelete(trigger);
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (triggerToDelete) {
|
||||
onRemove(triggerToDelete);
|
||||
setTriggerToDelete(null);
|
||||
setDeleteDialogOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="space-y-2">
|
||||
|
@ -147,7 +173,7 @@ export const ConfiguredTriggersList: React.FC<ConfiguredTriggersListProps> = ({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center">
|
||||
|
@ -185,7 +211,7 @@ export const ConfiguredTriggersList: React.FC<ConfiguredTriggersListProps> = ({
|
|||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onRemove(trigger)}
|
||||
onClick={() => handleDeleteClick(trigger)}
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
disabled={isLoading}
|
||||
>
|
||||
|
@ -200,6 +226,26 @@ export const ConfiguredTriggersList: React.FC<ConfiguredTriggersListProps> = ({
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Trigger</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete "{triggerToDelete?.name}"? This action cannot be undone and will stop all automated runs from this trigger.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-destructive hover:bg-destructive/90"
|
||||
>
|
||||
Delete Trigger
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue