mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
ef3101fbb7
commit
37c781a99b
|
@ -23,7 +23,6 @@ import { usePipedreamProfiles } from '@/hooks/react-query/pipedream/use-pipedrea
|
|||
import { useFeatureFlag } from '@/lib/feature-flags';
|
||||
|
||||
// Import components from existing pages
|
||||
import { UpdateAgentDialog } from '../../../components/agents/update-agent-dialog';
|
||||
import { SearchAndFilters } from '../../../components/agents/search-and-filters';
|
||||
import { ResultsInfo } from '../../../components/agents/results-info';
|
||||
import { EmptyState } from '../../../components/agents/empty-state';
|
||||
|
@ -1508,24 +1507,6 @@ export default function AgentsPage() {
|
|||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
{allTags.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-muted-foreground">Filter by tags:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{allTags.map(tag => (
|
||||
<Badge
|
||||
key={tag}
|
||||
variant={marketplaceSelectedTags.includes(tag) ? "default" : "outline"}
|
||||
className="cursor-pointer hover:bg-primary/80"
|
||||
onClick={() => handleTagFilter(tag)}
|
||||
>
|
||||
<Tags className="h-3 w-3 mr-1" />
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
|
@ -1941,17 +1922,6 @@ export default function AgentsPage() {
|
|||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Dialogs */}
|
||||
<UpdateAgentDialog
|
||||
agentId={editingAgentId}
|
||||
isOpen={editDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setEditDialogOpen(open);
|
||||
if (!open) setEditingAgentId(null);
|
||||
}}
|
||||
onAgentUpdated={loadAgents}
|
||||
/>
|
||||
|
||||
{/* Publish Dialog */}
|
||||
<Dialog open={!!publishDialog} onOpenChange={() => setPublishDialog(null)}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { X, Settings2, Brain, Database, Zap, Workflow, Bot } from 'lucide-react';
|
||||
import { AgentMCPConfiguration } from './agent-mcp-configuration';
|
||||
import { AgentTriggersConfiguration } from './triggers/agent-triggers-configuration';
|
||||
import { AgentWorkflowsConfiguration } from './workflows/agent-workflows-configuration';
|
||||
import { AgentKnowledgeBaseManager } from './knowledge-base/agent-knowledge-base-manager';
|
||||
import { AgentToolsConfiguration } from './agent-tools-configuration';
|
||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface AgentConfigModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: (agentId: string | undefined) => void;
|
||||
initialTab?: string;
|
||||
}
|
||||
|
||||
export const AgentConfigModal: React.FC<AgentConfigModalProps> = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
initialTab = 'integrations'
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState(initialTab);
|
||||
const [editingInstructions, setEditingInstructions] = useState(false);
|
||||
const [instructionsValue, setInstructionsValue] = useState('');
|
||||
const [agentName, setAgentName] = useState('');
|
||||
const [agentDescription, setAgentDescription] = useState('');
|
||||
|
||||
const { data: agent, isLoading } = useAgent(selectedAgentId || '');
|
||||
const updateAgentMutation = useUpdateAgent();
|
||||
const router = useRouter();
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
onAgentSelect?.(agentId);
|
||||
};
|
||||
|
||||
// Update local state when agent data changes
|
||||
React.useEffect(() => {
|
||||
if (agent) {
|
||||
setAgentName(agent.name || '');
|
||||
setAgentDescription(agent.description || '');
|
||||
setInstructionsValue(agent.system_prompt || '');
|
||||
}
|
||||
}, [agent]);
|
||||
|
||||
const handleSaveInstructions = async () => {
|
||||
if (!selectedAgentId) return;
|
||||
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId: selectedAgentId,
|
||||
name: agentName,
|
||||
description: agentDescription,
|
||||
system_prompt: instructionsValue
|
||||
});
|
||||
toast.success('Agent updated successfully');
|
||||
setEditingInstructions(false);
|
||||
} catch (error) {
|
||||
toast.error('Failed to update agent');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToolsChange = async (tools: Record<string, { enabled: boolean; description: string }>) => {
|
||||
if (!selectedAgentId) return;
|
||||
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId: selectedAgentId,
|
||||
agentpress_tools: tools
|
||||
});
|
||||
toast.success('Tools updated successfully');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update tools');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMCPChange = async (mcps: any) => {
|
||||
if (!selectedAgentId) return;
|
||||
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId: selectedAgentId,
|
||||
configured_mcps: mcps.configured_mcps || [],
|
||||
custom_mcps: mcps.custom_mcps || []
|
||||
});
|
||||
toast.success('Integrations updated successfully');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update integrations');
|
||||
}
|
||||
};
|
||||
|
||||
const displayName = agent?.name || 'Suna';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-5xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0 pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings2 className="h-5 w-5" />
|
||||
Agent Configuration
|
||||
</DialogTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-hidden flex flex-col">
|
||||
{/* Agent Selector */}
|
||||
<div className="flex-shrink-0 border-b pb-4 mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* <AgentSelector
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={handleAgentSelect}
|
||||
className="min-w-[250px]"
|
||||
/> */}
|
||||
</div>
|
||||
{selectedAgentId && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/agents/config/${selectedAgentId}`)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
Edit Agent
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration Tabs */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
|
||||
<TabsList className="grid w-full grid-cols-6 flex-shrink-0">
|
||||
<TabsTrigger value="integrations" className="flex items-center gap-2">
|
||||
<span className="hidden sm:inline">Integrations</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tools" className="flex items-center gap-2">
|
||||
<Settings2 className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Tools</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="instructions" className="flex items-center gap-2">
|
||||
<Brain className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Instructions</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="knowledge" className="flex items-center gap-2">
|
||||
<Database className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Knowledge Base</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="triggers" className="flex items-center gap-2">
|
||||
<Zap className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Triggers</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="workflows" className="flex items-center gap-2">
|
||||
<Workflow className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Workflows</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex-1 overflow-hidden mt-4">
|
||||
<TabsContent value="integrations" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4">
|
||||
<div className="text-center py-8">
|
||||
<h3 className="text-lg font-semibold mb-2">MCP Integrations</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Connect your agent to external services and tools
|
||||
</p>
|
||||
{selectedAgentId && (
|
||||
<AgentMCPConfiguration
|
||||
configuredMCPs={agent?.configured_mcps || []}
|
||||
customMCPs={agent?.custom_mcps || []}
|
||||
onMCPChange={handleMCPChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tools" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4 p-4">
|
||||
{selectedAgentId ? (
|
||||
<div className="max-w-4xl">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold mb-2">Standard Tools</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Configure the built-in tools available to your agent
|
||||
</p>
|
||||
</div>
|
||||
<AgentToolsConfiguration
|
||||
tools={agent?.agentpress_tools || {}}
|
||||
onToolsChange={handleToolsChange}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Settings2 className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">Standard Tools</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Select an agent to configure its tools
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="instructions" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4 p-4">
|
||||
{selectedAgentId ? (
|
||||
<div className="max-w-2xl">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-name">Agent Name</Label>
|
||||
<Input
|
||||
id="agent-name"
|
||||
value={agentName}
|
||||
onChange={(e) => setAgentName(e.target.value)}
|
||||
placeholder="Enter agent name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-description">Description</Label>
|
||||
<Input
|
||||
id="agent-description"
|
||||
value={agentDescription}
|
||||
onChange={(e) => setAgentDescription(e.target.value)}
|
||||
placeholder="Brief description of the agent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="system-instructions">System Instructions</Label>
|
||||
<Textarea
|
||||
id="system-instructions"
|
||||
value={instructionsValue}
|
||||
onChange={(e) => setInstructionsValue(e.target.value)}
|
||||
placeholder="Describe the agent's role, behavior, and expertise..."
|
||||
className="min-h-[200px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleSaveInstructions}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
>
|
||||
{updateAgentMutation.isPending ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setAgentName(agent?.name || '');
|
||||
setAgentDescription(agent?.description || '');
|
||||
setInstructionsValue(agent?.system_prompt || '');
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Brain className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">System Instructions</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Select an agent to configure its instructions
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="knowledge" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4">
|
||||
{selectedAgentId ? (
|
||||
<AgentKnowledgeBaseManager
|
||||
agentId={selectedAgentId}
|
||||
agentName={agentName}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Database className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">Knowledge Base</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Select an agent to manage its knowledge base
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="triggers" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4">
|
||||
{selectedAgentId ? (
|
||||
<AgentTriggersConfiguration agentId={selectedAgentId} />
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Zap className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">Triggers</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Select an agent to configure its triggers
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="workflows" className="h-full m-0 overflow-y-auto">
|
||||
<div className="space-y-4">
|
||||
{selectedAgentId ? (
|
||||
<AgentWorkflowsConfiguration
|
||||
agentId={selectedAgentId}
|
||||
agentName={agentName}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Workflow className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-lg font-semibold mb-2">Workflows</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Select an agent to configure its workflows
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,472 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Loader2, Search, Save, Settings2, Sparkles, GitBranch } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from './tools';
|
||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { MCPConfigurationNew } from './mcp/mcp-configuration-new';
|
||||
import { AgentVersionManager } from './AgentVersionManager';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
interface AgentUpdateRequest {
|
||||
name?: string;
|
||||
description?: string;
|
||||
system_prompt?: string;
|
||||
configured_mcps?: Array<{ name: string; qualifiedName: string; config: any; enabledTools?: string[] }>;
|
||||
custom_mcps?: Array<{ name: string; type: 'json' | 'sse'; config: any; enabledTools: string[] }>;
|
||||
agentpress_tools?: Record<string, { enabled: boolean; description: string }>;
|
||||
is_default?: boolean;
|
||||
}
|
||||
|
||||
interface UpdateAgentDialogProps {
|
||||
agentId: string | null;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onAgentUpdated?: () => void;
|
||||
}
|
||||
|
||||
const TOOL_CATEGORIES = ['All', 'AI', 'Code', 'Integration', 'Search', 'File', 'Data'];
|
||||
|
||||
export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdated }: UpdateAgentDialogProps) => {
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('All');
|
||||
const [formData, setFormData] = useState<AgentUpdateRequest>({});
|
||||
|
||||
const {
|
||||
data: agent,
|
||||
isLoading,
|
||||
error
|
||||
} = useAgent(agentId || '');
|
||||
|
||||
const updateAgentMutation = useUpdateAgent();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSearchQuery('');
|
||||
setSelectedCategory('All');
|
||||
setFormData({});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (agent && isOpen) {
|
||||
setFormData({
|
||||
name: agent.name,
|
||||
description: agent.description || '',
|
||||
system_prompt: agent.system_prompt,
|
||||
configured_mcps: (agent.configured_mcps || []).map(mcp => ({
|
||||
name: mcp.name,
|
||||
qualifiedName: (mcp as any).qualifiedName || mcp.name,
|
||||
config: mcp.config,
|
||||
enabledTools: (mcp as any).enabledTools || []
|
||||
})),
|
||||
custom_mcps: agent.custom_mcps || [],
|
||||
agentpress_tools: agent.agentpress_tools || {},
|
||||
is_default: agent.is_default,
|
||||
});
|
||||
}
|
||||
}, [agent, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error && isOpen) {
|
||||
console.error('Error loading agent:', error);
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to load agent');
|
||||
onOpenChange(false);
|
||||
}
|
||||
}, [error, isOpen, onOpenChange]);
|
||||
|
||||
const handleInputChange = (field: keyof AgentUpdateRequest, value: any) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleToolToggle = (toolName: string, enabled: boolean) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
agentpress_tools: {
|
||||
...prev.agentpress_tools,
|
||||
[toolName]: {
|
||||
...prev.agentpress_tools?.[toolName],
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const handleMCPConfigurationChange = (mcps: any[]) => {
|
||||
// Separate standard and custom MCPs
|
||||
const standardMcps = mcps.filter(mcp => !mcp.isCustom);
|
||||
const customMcps = mcps.filter(mcp => mcp.isCustom).map(mcp => ({
|
||||
name: mcp.name,
|
||||
type: mcp.customType as 'json' | 'sse',
|
||||
config: mcp.config,
|
||||
enabledTools: mcp.enabledTools || []
|
||||
}));
|
||||
|
||||
handleInputChange('configured_mcps', standardMcps);
|
||||
handleInputChange('custom_mcps', customMcps);
|
||||
};
|
||||
|
||||
const getAllAgentPressTools = () => {
|
||||
const existing = formData.agentpress_tools || {};
|
||||
const merged = { ...DEFAULT_AGENTPRESS_TOOLS };
|
||||
|
||||
Object.keys(existing).forEach(key => {
|
||||
merged[key] = { ...merged[key], ...existing[key] };
|
||||
});
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
const getSelectedToolsCount = (): number => {
|
||||
const tools = getAllAgentPressTools();
|
||||
return Object.values(tools).filter(tool => tool.enabled).length;
|
||||
};
|
||||
|
||||
const getFilteredTools = (): Array<[string, any]> => {
|
||||
let tools = Object.entries(getAllAgentPressTools());
|
||||
|
||||
if (searchQuery) {
|
||||
tools = tools.filter(([toolName, toolInfo]) =>
|
||||
getToolDisplayName(toolName).toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
toolInfo.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
return tools;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.name?.trim()) {
|
||||
toast.error('Agent name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.system_prompt?.trim()) {
|
||||
toast.error('System prompt is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!agentId) {
|
||||
toast.error('Invalid agent ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateAgentMutation.mutateAsync({
|
||||
agentId,
|
||||
...formData
|
||||
});
|
||||
|
||||
toast.success('Agent updated successfully!');
|
||||
onOpenChange(false);
|
||||
onAgentUpdated?.();
|
||||
} catch (error: any) {
|
||||
console.error('Error updating agent:', error);
|
||||
|
||||
if (error.message?.includes('System prompt cannot be empty')) {
|
||||
toast.error('System prompt cannot be empty');
|
||||
} else if (error.message?.includes('Failed to create new agent version')) {
|
||||
toast.error('Failed to create new version. Please try again.');
|
||||
} else if (error.message?.includes('Failed to update agent')) {
|
||||
toast.error('Failed to update agent. Please check your configuration and try again.');
|
||||
} else if (error.message?.includes('Agent not found')) {
|
||||
toast.error('Agent not found. It may have been deleted.');
|
||||
onOpenChange(false);
|
||||
} else if (error.message?.includes('Access denied')) {
|
||||
toast.error('You do not have permission to update this agent.');
|
||||
onOpenChange(false);
|
||||
} else {
|
||||
toast.error(error.message || 'Failed to update agent. Please try again.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
if (isLoading || !agent) {
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl h-[85vh] p-0 gap-0 flex flex-col">
|
||||
<DialogHeader className="px-6 py-4 border-b flex-shrink-0">
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
Edit Agent
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm mt-1">
|
||||
Loading agent configuration...
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 p-6">
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="space-y-2">
|
||||
<Skeleton className="h-6 w-48" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl h-[85vh] p-0 gap-0 flex flex-col">
|
||||
<DialogHeader className="px-6 py-4 border-b flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<DialogTitle className="text-xl font-semibold flex items-center gap-2">
|
||||
Edit Agent
|
||||
{(agent as any).current_version && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{(agent as any).current_version.version_name}
|
||||
</Badge>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm mt-1">
|
||||
Modify your agent's configuration and capabilities
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 w-full overflow-hidden min-h-0">
|
||||
<div className="flex w-full h-full">
|
||||
{/* Left Panel - Basic Configuration */}
|
||||
<div className="p-6 py-4 w-[40%] space-y-6 overflow-y-auto scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-name" className="text-sm font-medium">
|
||||
Agent Name
|
||||
</Label>
|
||||
<Input
|
||||
id="agent-name"
|
||||
value={formData.name || ''}
|
||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||
placeholder="e.g., Research Assistant"
|
||||
className="h-10"
|
||||
disabled={updateAgentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-description" className="text-sm font-medium">
|
||||
Description
|
||||
</Label>
|
||||
<Input
|
||||
id="agent-description"
|
||||
value={formData.description || ''}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
placeholder="Brief description of the agent"
|
||||
className="h-10"
|
||||
disabled={updateAgentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 flex-1">
|
||||
<Label htmlFor="system-instructions" className="text-sm font-medium">
|
||||
System Instructions
|
||||
</Label>
|
||||
<Textarea
|
||||
id="system-instructions"
|
||||
value={formData.system_prompt || ''}
|
||||
onChange={(e) => handleInputChange('system_prompt', e.target.value)}
|
||||
placeholder="Describe the agent's role, behavior, and expertise..."
|
||||
className="min-h-[250px] resize-none"
|
||||
disabled={updateAgentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Tools & MCP */}
|
||||
<div className="border-l w-[60%] bg-muted/30 flex flex-col min-h-0">
|
||||
<Tabs defaultValue="tools" className="flex flex-col h-full">
|
||||
<TabsList className="w-full justify-start rounded-none border-b h-10">
|
||||
<TabsTrigger
|
||||
value="tools"
|
||||
>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
Default Tools
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="mcp"
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
MCP Servers
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="versions"
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
Versions
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="tools" className="flex-1 flex flex-col m-0 min-h-0">
|
||||
<div className="px-6 py-4 border-b bg-background flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">Available Tools</h3>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{getSelectedToolsCount()} selected
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search tools..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 h-10"
|
||||
disabled={updateAgentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{TOOL_CATEGORIES.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
variant={selectedCategory === category ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
className="px-3 py-1.5 h-auto text-xs font-medium rounded-full"
|
||||
>
|
||||
{category}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent p-6 min-h-0">
|
||||
<div className="space-y-3">
|
||||
{getFilteredTools().map(([toolName, toolInfo]) => (
|
||||
<div
|
||||
key={toolName}
|
||||
className="flex items-center gap-3 p-3 bg-card rounded-lg border hover:border-border/80 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={toolInfo.enabled || false}
|
||||
onCheckedChange={(checked) => handleToolToggle(toolName, checked)}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
className="flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
{toolInfo.description}
|
||||
</p>
|
||||
</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>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="mcp" className="flex-1 m-0 p-6 overflow-y-auto">
|
||||
<MCPConfigurationNew
|
||||
configuredMCPs={[
|
||||
...(formData.configured_mcps || []).map(mcp => ({
|
||||
...mcp,
|
||||
enabledTools: mcp.enabledTools || []
|
||||
})),
|
||||
...(formData.custom_mcps || []).map(customMcp => ({
|
||||
name: customMcp.name,
|
||||
qualifiedName: `custom_${customMcp.type}_${customMcp.name.replace(' ', '_').toLowerCase()}`,
|
||||
config: customMcp.config,
|
||||
enabledTools: customMcp.enabledTools,
|
||||
isCustom: true,
|
||||
customType: customMcp.type
|
||||
}))
|
||||
]}
|
||||
onConfigurationChange={handleMCPConfigurationChange}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="versions" className="flex-1 m-0 p-6 overflow-y-auto">
|
||||
<AgentVersionManager
|
||||
agent={agent as any}
|
||||
onCreateVersion={() => {
|
||||
// When creating a new version, save current changes first
|
||||
handleSubmit();
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 border-t py-4 flex-shrink-0">
|
||||
<div className="space-y-3">
|
||||
{/* Show notice if changes will create a new version */}
|
||||
{agent && (formData.system_prompt !== agent.system_prompt ||
|
||||
JSON.stringify(formData.configured_mcps) !== JSON.stringify(agent.configured_mcps) ||
|
||||
JSON.stringify(formData.custom_mcps) !== JSON.stringify(agent.custom_mcps) ||
|
||||
JSON.stringify(formData.agentpress_tools) !== JSON.stringify(agent.agentpress_tools)) && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 px-3 py-2 rounded-md">
|
||||
<GitBranch className="h-4 w-4" />
|
||||
<span>These changes will create a new version of your agent</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
disabled={updateAgentMutation.isPending}
|
||||
className="px-6"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={updateAgentMutation.isPending || !formData.name?.trim() || !formData.system_prompt?.trim()}
|
||||
>
|
||||
{updateAgentMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Saving Changes
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Changes
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -1,329 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronDown, Plus, Star, Bot, Edit, User } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CreateAgentDialog } from '@/components/agents/create-agent-dialog';
|
||||
import { useFeatureFlags } from '@/lib/feature-flags';
|
||||
|
||||
interface AgentSelectorProps {
|
||||
onAgentSelect?: (agentId: string | undefined) => void;
|
||||
selectedAgentId?: string;
|
||||
className?: string;
|
||||
variant?: 'default' | 'heading';
|
||||
}
|
||||
|
||||
export function AgentSelector({
|
||||
onAgentSelect,
|
||||
selectedAgentId,
|
||||
className,
|
||||
variant = 'default',
|
||||
}: AgentSelectorProps) {
|
||||
const { data: agentsResponse, isLoading, refetch: loadAgents } = useAgents({
|
||||
limit: 100,
|
||||
sort_by: 'name',
|
||||
sort_order: 'asc'
|
||||
});
|
||||
|
||||
|
||||
const { flags, loading: flagsLoading } = useFeatureFlags(['custom_agents']);
|
||||
const customAgentsEnabled = flags.custom_agents;
|
||||
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
|
||||
const agents = agentsResponse?.agents || [];
|
||||
const defaultAgent = agents.find(agent => agent.is_default);
|
||||
const currentAgent = selectedAgentId
|
||||
? agents.find(agent => agent.agent_id === selectedAgentId)
|
||||
: null;
|
||||
|
||||
const displayName = currentAgent?.name || defaultAgent?.name || 'Suna';
|
||||
const agentAvatar = currentAgent?.avatar;
|
||||
const isUsingSuna = !currentAgent && !defaultAgent;
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
onAgentSelect?.(agentId);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleCreateAgent = () => {
|
||||
setCreateDialogOpen(true);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleManageAgents = () => {
|
||||
router.push('/agents');
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleClearSelection = () => {
|
||||
onAgentSelect?.(undefined);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
if (!customAgentsEnabled) {
|
||||
if (variant === 'heading') {
|
||||
return (
|
||||
<div className={cn("flex items-center", className)}>
|
||||
<span className="tracking-tight text-4xl font-semibold leading-tight text-primary">
|
||||
Suna
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
if (variant === 'heading') {
|
||||
return (
|
||||
<div className={cn("flex items-center", className)}>
|
||||
<span className="tracking-tight text-4xl font-semibold leading-tight text-muted-foreground">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded-lg border bg-background">
|
||||
<Bot className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">Loading agents...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'heading') {
|
||||
return (
|
||||
<>
|
||||
<div className={cn("flex items-center", className)}>
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center gap-1 px-2 py-1 h-auto hover:bg-transparent hover:text-primary transition-colors group"
|
||||
>
|
||||
<span className="underline decoration-dashed underline-offset-6 decoration-muted-foreground/50 tracking-tight text-4xl font-semibold leading-tight text-primary">
|
||||
{displayName}
|
||||
<span className="text-muted-foreground ml-2">
|
||||
{agentAvatar && agentAvatar}
|
||||
</span>
|
||||
</span>
|
||||
<div className="flex items-center opacity-60 group-hover:opacity-100 transition-opacity">
|
||||
<ChevronDown className="h-5 w-5 text-muted-foreground" />
|
||||
<Edit className="h-4 w-4 text-muted-foreground ml-1" />
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="start" className="w-[320px]">
|
||||
<div className="px-3 py-2">
|
||||
<p className="text-sm font-medium">Select an agent</p>
|
||||
<p className="text-xs text-muted-foreground">You can create your own agent</p>
|
||||
</div>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleClearSelection()}
|
||||
className="flex flex-col items-start gap-1 p-3 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="font-medium truncate">Suna</span>
|
||||
<Badge variant="outline" className="text-xs px-1 py-0 flex-shrink-0">
|
||||
Default
|
||||
</Badge>
|
||||
</div>
|
||||
{isUsingSuna && (
|
||||
<div className="h-2 w-2 rounded-full bg-primary flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground pl-6 line-clamp-2">
|
||||
Your personal AI employee
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{agents.length > 0 ? (
|
||||
<>
|
||||
{agents.map((agent) => (
|
||||
<DropdownMenuItem
|
||||
key={agent.agent_id}
|
||||
onClick={() => handleAgentSelect(agent.agent_id)}
|
||||
className="flex flex-col items-start gap-1 p-3 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
{agent.avatar}
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="font-medium truncate">{agent.name}</span>
|
||||
{agent.is_default && (
|
||||
<Badge variant="secondary" className="text-xs px-1 py-0 flex-shrink-0">
|
||||
<Star className="h-2.5 w-2.5 mr-0.5 fill-current" />
|
||||
System
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{currentAgent?.agent_id === agent.agent_id && (
|
||||
<div className="h-2 w-2 rounded-full bg-primary flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
{agent.description && (
|
||||
<span className="text-xs text-muted-foreground pl-6 line-clamp-2">
|
||||
{agent.description}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={handleCreateAgent} className="cursor-pointer">
|
||||
Agents
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex items-center gap-2 px-3 py-2 h-auto min-w-[200px] justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isUsingSuna ? (
|
||||
<User className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Bot className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm font-medium">
|
||||
{displayName}
|
||||
</span>
|
||||
{isUsingSuna && (
|
||||
<Badge variant="outline" className="text-xs px-1 py-0">
|
||||
Default
|
||||
</Badge>
|
||||
)}
|
||||
{currentAgent?.is_default && (
|
||||
<Badge variant="secondary" className="text-xs px-1 py-0">
|
||||
<Star className="h-2.5 w-2.5 mr-0.5 fill-current" />
|
||||
System
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{currentAgent?.description ? (
|
||||
<span className="text-xs text-muted-foreground line-clamp-1 max-w-[150px]">
|
||||
{currentAgent.description}
|
||||
</span>
|
||||
) : isUsingSuna ? (
|
||||
<span className="text-xs text-muted-foreground line-clamp-1 max-w-[150px]">
|
||||
Your personal AI employee
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="start" className="w-[280px]">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleClearSelection()}
|
||||
className="flex flex-col items-start gap-1 p-3 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<span className="font-medium">Suna</span>
|
||||
<Badge variant="outline" className="text-xs px-1 py-0">
|
||||
Default
|
||||
</Badge>
|
||||
</div>
|
||||
{isUsingSuna && (
|
||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground pl-6 line-clamp-2">
|
||||
Your personal AI employee
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{agents.length > 0 ? (
|
||||
<>
|
||||
{agents.map((agent) => (
|
||||
<DropdownMenuItem
|
||||
key={agent.agent_id}
|
||||
onClick={() => handleAgentSelect(agent.agent_id)}
|
||||
className="flex flex-col items-start gap-1 p-3 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Bot className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<span className="font-medium">{agent.name}</span>
|
||||
{agent.is_default && (
|
||||
<Badge variant="secondary" className="text-xs px-1 py-0">
|
||||
<Star className="h-2.5 w-2.5 mr-0.5 fill-current" />
|
||||
System
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{currentAgent?.agent_id === agent.agent_id && (
|
||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
||||
)}
|
||||
</div>
|
||||
{agent.description && (
|
||||
<span className="text-xs text-muted-foreground pl-6 line-clamp-2">
|
||||
{agent.description}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={handleCreateAgent} className="cursor-pointer">
|
||||
<Plus className="h-4 w-4" />
|
||||
Create New Agent
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleManageAgents} className="cursor-pointer">
|
||||
<Bot className="h-4 w-4" />
|
||||
Manage All Agents
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<CreateAgentDialog
|
||||
isOpen={createDialogOpen}
|
||||
onOpenChange={setCreateDialogOpen}
|
||||
onAgentCreated={loadAgents}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -15,10 +15,10 @@ import { useModelSelection } from './_use-model-selection';
|
|||
import { useFileDelete } from '@/hooks/react-query/files';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { FloatingToolPreview, ToolCallInput } from './floating-tool-preview';
|
||||
import { Settings2, Sparkles, Brain, ChevronRight, Zap, Workflow } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Settings2, Sparkles, Brain, ChevronRight, Zap, Workflow, Database, Wrench } from 'lucide-react';
|
||||
import { FaGoogle, FaDiscord } from 'react-icons/fa';
|
||||
import { SiNotion } from 'react-icons/si';
|
||||
import { AgentConfigModal } from '@/components/agents/agent-config-modal';
|
||||
|
||||
export interface ChatInputHandles {
|
||||
getPendingFiles: () => File[];
|
||||
|
@ -105,6 +105,8 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
|
|||
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||
const [configModalTab, setConfigModalTab] = useState('integrations');
|
||||
|
||||
const {
|
||||
selectedModel,
|
||||
|
@ -322,47 +324,82 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
|
|||
</CardContent>
|
||||
|
||||
{enableAdvancedConfig && selectedAgentId && (
|
||||
<div
|
||||
className="w-full border-t border-border/30 bg-muted/20 px-4 py-2.5 rounded-b-3xl border-l border-r border-b border-border cursor-pointer hover:bg-muted/30 transition-colors"
|
||||
onClick={() => onConfigureAgent?.(selectedAgentId)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 text-xs min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<span className="hidden sm:inline">Integrations</span>
|
||||
<span className="sm:hidden">Integrations</span>
|
||||
<div className="flex items-center -space-x-1 ml-1">
|
||||
<div className="w-5 h-5 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaGoogle className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
<div className="w-5 h-5 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaDiscord className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
<div className="w-5 h-5 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<SiNotion className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
<div className="w-full border-t border-border/30 bg-muted/20 px-4 py-2.5 rounded-b-3xl border-l border-r border-b border-border">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex items-center gap-1 sm:gap-2 overflow-x-auto scrollbar-none">
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigModalTab('integrations');
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-all duration-200 px-2.5 py-1.5 rounded-md hover:bg-muted/50 border border-transparent hover:border-border/30 flex-shrink-0"
|
||||
>
|
||||
<div className="flex items-center -space-x-0.5">
|
||||
<div className="w-4 h-4 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaGoogle className="w-2 h-2" />
|
||||
</div>
|
||||
<div className="w-4 h-4 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaDiscord className="w-2 h-2" />
|
||||
</div>
|
||||
<div className="w-4 h-4 bg-white border border-border rounded-full flex items-center justify-center shadow-sm">
|
||||
<SiNotion className="w-2 h-2" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1 h-1 bg-muted-foreground/60 rounded-full hidden sm:block" />
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Brain className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
<span className="hidden sm:inline">Instructions</span>
|
||||
<span className="sm:hidden">Instructions</span>
|
||||
</div>
|
||||
<div className="w-1 h-1 bg-muted-foreground/60 rounded-full hidden sm:block" />
|
||||
<div className="flex items-center gap-1 text-muted-foreground hidden sm:flex">
|
||||
<Zap className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
<span>Triggers</span>
|
||||
</div>
|
||||
<div className="w-1 h-1 bg-muted-foreground/60 rounded-full hidden sm:block" />
|
||||
<div className="flex items-center gap-1 text-muted-foreground hidden sm:flex">
|
||||
<Workflow className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
<span>Workflows</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs font-medium">Integrations</span>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-4 bg-border/60" />
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigModalTab('instructions');
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-all duration-200 px-2.5 py-1.5 rounded-md hover:bg-muted/50 border border-transparent hover:border-border/30 flex-shrink-0"
|
||||
>
|
||||
<Brain className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span className="text-xs font-medium">Instructions</span>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-4 bg-border/60" />
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigModalTab('knowledge');
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-all duration-200 px-2.5 py-1.5 rounded-md hover:bg-muted/50 border border-transparent hover:border-border/30 flex-shrink-0"
|
||||
>
|
||||
<Database className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span className="text-xs font-medium">Knowledge</span>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-4 bg-border/60" />
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigModalTab('triggers');
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-all duration-200 px-2.5 py-1.5 rounded-md hover:bg-muted/50 border border-transparent hover:border-border/30 flex-shrink-0"
|
||||
>
|
||||
<Zap className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span className="text-xs font-medium">Triggers</span>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-4 bg-border/60" />
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigModalTab('workflows');
|
||||
setConfigModalOpen(true);
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground transition-all duration-200 px-2.5 py-1.5 rounded-md hover:bg-muted/50 border border-transparent hover:border-border/30 flex-shrink-0"
|
||||
>
|
||||
<Workflow className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span className="text-xs font-medium">Workflows</span>
|
||||
</button>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -382,6 +419,15 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
|
|||
</motion.div>
|
||||
)} */}
|
||||
|
||||
{/* Agent Configuration Modal */}
|
||||
<AgentConfigModal
|
||||
isOpen={configModalOpen}
|
||||
onOpenChange={setConfigModalOpen}
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
initialTab={configModalTab}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue