mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
37c781a99b
commit
87da181fc5
|
@ -377,7 +377,7 @@ async def get_pipedream_apps(
|
|||
|
||||
data = response.json()
|
||||
|
||||
print(data)
|
||||
# print(data)
|
||||
|
||||
logger.info(f"Successfully fetched {len(data.get('data', []))} apps from Pipedream registry")
|
||||
return {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
|||
import { useRouter } from 'next/navigation';
|
||||
|
||||
// Import hooks and components from existing pages
|
||||
import { useAgents, useUpdateAgent, useDeleteAgent, useOptimisticAgentUpdate, useCreateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useAgents, useUpdateAgent, useDeleteAgent, useOptimisticAgentUpdate, useCreateNewAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useMarketplaceTemplates, useInstallTemplate, useMyTemplates, useUnpublishTemplate, usePublishTemplate } from '@/hooks/react-query/secure-mcp/use-secure-mcp';
|
||||
import { useCreateCredentialProfile, type CreateCredentialProfileRequest } from '@/hooks/react-query/mcp/use-credential-profiles';
|
||||
import { useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers';
|
||||
|
@ -108,13 +108,7 @@ interface MissingProfile {
|
|||
required_config: string[];
|
||||
}
|
||||
|
||||
interface AgentPreviewSheetProps {
|
||||
item: MarketplaceTemplate | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onInstall: (item: MarketplaceTemplate) => void;
|
||||
isInstalling: boolean;
|
||||
}
|
||||
|
||||
|
||||
interface InstallDialogProps {
|
||||
item: MarketplaceTemplate | null;
|
||||
|
@ -124,158 +118,7 @@ interface InstallDialogProps {
|
|||
isInstalling: boolean;
|
||||
}
|
||||
|
||||
const AgentPreviewSheet: React.FC<AgentPreviewSheetProps> = ({
|
||||
item,
|
||||
open,
|
||||
onOpenChange,
|
||||
onInstall,
|
||||
isInstalling
|
||||
}) => {
|
||||
if (!item) return null;
|
||||
|
||||
const { avatar, color } = item.avatar && item.avatar_color
|
||||
? { avatar: item.avatar, color: item.avatar_color }
|
||||
: getAgentAvatar(item.id);
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent>
|
||||
<SheetHeader className="space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className="h-16 w-16 flex items-center justify-center rounded-xl shrink-0"
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
<div className="text-3xl">{avatar}</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<SheetTitle className="text-xl font-semibold line-clamp-2">
|
||||
{item.name}
|
||||
</SheetTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<User className="h-4 w-4" />
|
||||
<span>{item.creator_name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Download className="h-4 w-4" />
|
||||
<span>{item.download_count} downloads</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => onInstall(item)}
|
||||
disabled={isInstalling}
|
||||
size='sm'
|
||||
className='w-48'
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Installing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4" />
|
||||
Add to Library
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</SheetHeader>
|
||||
<div className="px-4 space-y-6 py-6">
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-xs text-muted-foreground uppercase tracking-wide">
|
||||
Description
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed">
|
||||
{item.description || 'No description available for this agent.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{item.tags && item.tags.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-sm text-muted-foreground uppercase tracking-wide">
|
||||
Tags
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{item.tags.map(tag => (
|
||||
<Badge key={tag} variant="outline" className="text-xs">
|
||||
<Tags className="h-3 w-3 mr-1" />
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{item.mcp_requirements && item.mcp_requirements.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-xs text-muted-foreground uppercase tracking-wide">
|
||||
Required Tools & MCPs
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{item.mcp_requirements.map((mcp, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-muted-foreground/10 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10">
|
||||
<Wrench className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-sm">{mcp.display_name}</div>
|
||||
{mcp.enabled_tools && mcp.enabled_tools.length > 0 && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{mcp.enabled_tools.length} tool{mcp.enabled_tools.length !== 1 ? 's' : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{mcp.custom_type && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{mcp.custom_type.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{item.metadata?.source_version_name && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-sm text-muted-foreground uppercase tracking-wide">
|
||||
Version
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{item.metadata.source_version_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{item.marketplace_published_at && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-xs text-muted-foreground uppercase tracking-wide">
|
||||
Published
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm">{formatDate(item.marketplace_published_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
|
||||
const InstallDialog: React.FC<InstallDialogProps> = ({
|
||||
item,
|
||||
|
@ -939,7 +782,6 @@ export default function AgentsPage() {
|
|||
const [installingItemId, setInstallingItemId] = useState<string | null>(null);
|
||||
const [selectedItem, setSelectedItem] = useState<MarketplaceTemplate | null>(null);
|
||||
const [showInstallDialog, setShowInstallDialog] = useState(false);
|
||||
const [showPreviewSheet, setShowPreviewSheet] = useState(false);
|
||||
|
||||
// Templates state
|
||||
const [templatesActioningId, setTemplatesActioningId] = useState<string | null>(null);
|
||||
|
@ -987,7 +829,7 @@ export default function AgentsPage() {
|
|||
|
||||
const updateAgentMutation = useUpdateAgent();
|
||||
const deleteAgentMutation = useDeleteAgent();
|
||||
const createAgentMutation = useCreateAgent();
|
||||
const createNewAgentMutation = useCreateNewAgent();
|
||||
const { optimisticallyUpdateAgent, revertOptimisticUpdate } = useOptimisticAgentUpdate();
|
||||
const installTemplateMutation = useInstallTemplate();
|
||||
const unpublishMutation = useUnpublishTemplate();
|
||||
|
@ -1138,44 +980,11 @@ export default function AgentsPage() {
|
|||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCreateNewAgent = async () => {
|
||||
try {
|
||||
const { avatar, avatar_color } = generateRandomAvatar();
|
||||
|
||||
const defaultAgentData = {
|
||||
name: 'New Agent',
|
||||
description: 'A newly created agent',
|
||||
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
||||
avatar,
|
||||
avatar_color,
|
||||
configured_mcps: [],
|
||||
agentpress_tools: Object.fromEntries(
|
||||
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
||||
key,
|
||||
{ enabled: value.enabled, description: value.description }
|
||||
])
|
||||
),
|
||||
is_default: false,
|
||||
};
|
||||
|
||||
const newAgent = await createAgentMutation.mutateAsync(defaultAgentData);
|
||||
router.push(`/agents/config/${newAgent.agent_id}`);
|
||||
} catch (error) {
|
||||
console.error('Error creating agent:', error);
|
||||
}
|
||||
const handleCreateNewAgent = () => {
|
||||
createNewAgentMutation.mutate();
|
||||
};
|
||||
|
||||
// Marketplace handlers
|
||||
const handleItemClick = (item: MarketplaceTemplate) => {
|
||||
setSelectedItem(item);
|
||||
setShowPreviewSheet(true);
|
||||
};
|
||||
|
||||
const handlePreviewInstall = (item: MarketplaceTemplate) => {
|
||||
setShowPreviewSheet(false);
|
||||
setShowInstallDialog(true);
|
||||
};
|
||||
|
||||
const handleInstallClick = (item: MarketplaceTemplate, e?: React.MouseEvent) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
|
@ -1288,13 +1097,6 @@ export default function AgentsPage() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleTagFilter = (tag: string) => {
|
||||
setMarketplaceSelectedTags(prev =>
|
||||
prev.includes(tag)
|
||||
? prev.filter(t => t !== tag)
|
||||
: [...prev, tag]
|
||||
);
|
||||
};
|
||||
|
||||
const getItemStyling = (item: MarketplaceTemplate) => {
|
||||
if (item.avatar && item.avatar_color) {
|
||||
|
@ -1560,7 +1362,7 @@ export default function AgentsPage() {
|
|||
<div
|
||||
key={item.id}
|
||||
className="bg-neutral-100 dark:bg-sidebar border border-border rounded-2xl overflow-hidden hover:bg-muted/50 transition-all duration-200 cursor-pointer group flex flex-col h-full"
|
||||
onClick={() => handleItemClick(item)}
|
||||
onClick={() => handleInstallClick(item)}
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className={`h-12 w-12 flex items-center justify-center rounded-lg`} style={{ backgroundColor: color }}>
|
||||
|
@ -1658,7 +1460,7 @@ export default function AgentsPage() {
|
|||
<div
|
||||
key={item.id}
|
||||
className="bg-neutral-100 dark:bg-sidebar border border-border rounded-2xl overflow-hidden hover:bg-muted/50 transition-all duration-200 cursor-pointer group flex flex-col h-full"
|
||||
onClick={() => handleItemClick(item)}
|
||||
onClick={() => handleInstallClick(item)}
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className={`h-12 w-12 flex items-center justify-center rounded-lg`} style={{ backgroundColor: color }}>
|
||||
|
@ -1975,13 +1777,6 @@ export default function AgentsPage() {
|
|||
</Dialog>
|
||||
|
||||
{/* Marketplace Dialogs */}
|
||||
<AgentPreviewSheet
|
||||
item={selectedItem}
|
||||
open={showPreviewSheet}
|
||||
onOpenChange={setShowPreviewSheet}
|
||||
onInstall={handlePreviewInstall}
|
||||
isInstalling={installingItemId === selectedItem?.id}
|
||||
/>
|
||||
<InstallDialog
|
||||
item={selectedItem}
|
||||
open={showInstallDialog}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { AgentTriggersConfiguration } from './triggers/agent-triggers-configurat
|
|||
import { AgentWorkflowsConfiguration } from './workflows/agent-workflows-configuration';
|
||||
import { AgentKnowledgeBaseManager } from './knowledge-base/agent-knowledge-base-manager';
|
||||
import { AgentToolsConfiguration } from './agent-tools-configuration';
|
||||
import { AgentSelector } from '../thread/chat-input/agent-selector';
|
||||
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
|
@ -42,9 +43,12 @@ export const AgentConfigModal: React.FC<AgentConfigModalProps> = ({
|
|||
const updateAgentMutation = useUpdateAgent();
|
||||
const router = useRouter();
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
onAgentSelect?.(agentId);
|
||||
};
|
||||
// Update active tab when initialTab changes or modal opens
|
||||
React.useEffect(() => {
|
||||
if (isOpen && initialTab) {
|
||||
setActiveTab(initialTab);
|
||||
}
|
||||
}, [initialTab, isOpen]);
|
||||
|
||||
// Update local state when agent data changes
|
||||
React.useEffect(() => {
|
||||
|
@ -108,19 +112,10 @@ export const AgentConfigModal: React.FC<AgentConfigModalProps> = ({
|
|||
<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>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings2 className="h-5 w-5" />
|
||||
Agent Configuration
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-hidden flex flex-col">
|
||||
|
@ -128,11 +123,10 @@ export const AgentConfigModal: React.FC<AgentConfigModalProps> = ({
|
|||
<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]"
|
||||
/> */}
|
||||
<AgentSelector
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
/>
|
||||
</div>
|
||||
{selectedAgentId && (
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
|
@ -93,18 +93,30 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
|
|||
)}
|
||||
|
||||
{configuredMCPs.length > 0 && (
|
||||
<div className="bg-card rounded-xl border border-border overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-border bg-muted/30">
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
Configured Integrations
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-card rounded-xl border border-border overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-border bg-muted/30">
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
Configured Integrations
|
||||
</h4>
|
||||
</div>
|
||||
<div className="p-2 divide-y divide-border">
|
||||
<ConfiguredMcpList
|
||||
configuredMCPs={configuredMCPs}
|
||||
onEdit={handleEditMCP}
|
||||
onRemove={handleRemoveMCP}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2 divide-y divide-border">
|
||||
<ConfiguredMcpList
|
||||
configuredMCPs={configuredMCPs}
|
||||
onEdit={handleEditMCP}
|
||||
onRemove={handleRemoveMCP}
|
||||
/>
|
||||
<div className="flex gap-2 justify-center">
|
||||
<Button onClick={() => setShowRegistryDialog(true)} variant="default">
|
||||
<Store className="h-4 w-4 mr-2" />
|
||||
Browse Apps
|
||||
</Button>
|
||||
<Button onClick={() => setShowCustomDialog(true)} variant="outline">
|
||||
<Server className="h-4 w-4 mr-2" />
|
||||
Custom MCP
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus, FileText, Loader2 } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCreateAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { DEFAULT_AGENTPRESS_TOOLS } from './tools';
|
||||
import { generateRandomAvatar } from '../../lib/utils/_avatar-generator';
|
||||
|
||||
interface ResultsInfoProps {
|
||||
isLoading: boolean;
|
||||
|
@ -21,35 +15,6 @@ export const ResultsInfo = ({
|
|||
currentPage,
|
||||
totalPages
|
||||
}: ResultsInfoProps) => {
|
||||
const router = useRouter();
|
||||
const createAgentMutation = useCreateAgent();
|
||||
|
||||
const handleCreateNewAgent = async () => {
|
||||
try {
|
||||
const { avatar, avatar_color } = generateRandomAvatar();
|
||||
|
||||
const defaultAgentData = {
|
||||
name: 'New Agent',
|
||||
description: 'A newly created agent',
|
||||
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
||||
avatar,
|
||||
avatar_color,
|
||||
configured_mcps: [],
|
||||
agentpress_tools: Object.fromEntries(
|
||||
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
||||
key,
|
||||
{ enabled: value.enabled, description: value.description }
|
||||
])
|
||||
),
|
||||
is_default: false,
|
||||
};
|
||||
|
||||
const newAgent = await createAgentMutation.mutateAsync(defaultAgentData);
|
||||
router.push(`/agents/config/${newAgent.agent_id}`);
|
||||
} catch (error) {
|
||||
console.error('Error creating agent:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading || totalAgents === 0) {
|
||||
return null;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
export const DEFAULT_AGENTPRESS_TOOLS: Record<string, { enabled: boolean; description: string; icon: string; color: string }> = {
|
||||
'sb_shell_tool': { enabled: false, description: 'Execute shell commands in tmux sessions for terminal operations, CLI tools, and system management', icon: '💻', color: 'bg-slate-100 dark:bg-slate-800' },
|
||||
'sb_files_tool': { enabled: false, description: 'Create, read, update, and delete files in the workspace with comprehensive file management', icon: '📁', color: 'bg-blue-100 dark:bg-blue-800/50' },
|
||||
'sb_browser_tool': { enabled: false, description: 'Browser automation for web navigation, clicking, form filling, and page interaction', icon: '🌐', color: 'bg-indigo-100 dark:bg-indigo-800/50' },
|
||||
'sb_deploy_tool': { enabled: false, description: 'Deploy applications and services with automated deployment capabilities', icon: '🚀', color: 'bg-green-100 dark:bg-green-800/50' },
|
||||
'sb_expose_tool': { enabled: false, description: 'Expose services and manage ports for application accessibility', icon: '🔌', color: 'bg-orange-100 dark:bg-orange-800/20' },
|
||||
'web_search_tool': { enabled: false, description: 'Search the web using Tavily API and scrape webpages with Firecrawl for research', icon: '🔍', color: 'bg-yellow-100 dark:bg-yellow-800/50' },
|
||||
'sb_vision_tool': { enabled: false, description: 'Vision and image processing capabilities for visual content analysis', icon: '👁️', color: 'bg-pink-100 dark:bg-pink-800/50' },
|
||||
'data_providers_tool': { enabled: false, description: 'Access to data providers and external APIs (requires RapidAPI key)', icon: '🔗', color: 'bg-cyan-100 dark:bg-cyan-800/50' },
|
||||
'sb_shell_tool': { enabled: true, description: 'Execute shell commands in tmux sessions for terminal operations, CLI tools, and system management', icon: '💻', color: 'bg-slate-100 dark:bg-slate-800' },
|
||||
'sb_files_tool': { enabled: true, description: 'Create, read, update, and delete files in the workspace with comprehensive file management', icon: '📁', color: 'bg-blue-100 dark:bg-blue-800/50' },
|
||||
'sb_browser_tool': { enabled: true, description: 'Browser automation for web navigation, clicking, form filling, and page interaction', icon: '🌐', color: 'bg-indigo-100 dark:bg-indigo-800/50' },
|
||||
'sb_deploy_tool': { enabled: true, description: 'Deploy applications and services with automated deployment capabilities', icon: '🚀', color: 'bg-green-100 dark:bg-green-800/50' },
|
||||
'sb_expose_tool': { enabled: true, description: 'Expose services and manage ports for application accessibility', icon: '🔌', color: 'bg-orange-100 dark:bg-orange-800/20' },
|
||||
'web_search_tool': { enabled: true, description: 'Search the web using Tavily API and scrape webpages with Firecrawl for research', icon: '🔍', color: 'bg-yellow-100 dark:bg-yellow-800/50' },
|
||||
'sb_vision_tool': { enabled: true, description: 'Vision and image processing capabilities for visual content analysis', icon: '👁️', color: 'bg-pink-100 dark:bg-pink-800/50' },
|
||||
'data_providers_tool': { enabled: true, description: 'Access to data providers and external APIs (requires RapidAPI key)', icon: '🔗', color: 'bg-cyan-100 dark:bg-cyan-800/50' },
|
||||
};
|
||||
|
||||
export const getToolDisplayName = (toolName: string): string => {
|
||||
|
|
|
@ -17,8 +17,8 @@ import {
|
|||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import { ChatSettingsDialog } from './chat-settings-dialog';
|
||||
import { useAgents, useCreateNewAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { cn, truncateString } from '@/lib/utils';
|
||||
|
||||
|
@ -47,38 +47,26 @@ const PREDEFINED_AGENTS: PredefinedAgent[] = [
|
|||
// }
|
||||
];
|
||||
|
||||
interface ChatSettingsDropdownProps {
|
||||
interface AgentSelectorProps {
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: (agentId: string | undefined) => void;
|
||||
selectedModel: string;
|
||||
onModelChange: (model: string) => void;
|
||||
modelOptions: any[];
|
||||
subscriptionStatus: any;
|
||||
canAccessModel: (modelId: string) => boolean;
|
||||
refreshCustomModels?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const ChatSettingsDropdown: React.FC<ChatSettingsDropdownProps> = ({
|
||||
export const AgentSelector: React.FC<AgentSelectorProps> = ({
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
selectedModel,
|
||||
onModelChange,
|
||||
modelOptions,
|
||||
subscriptionStatus,
|
||||
canAccessModel,
|
||||
refreshCustomModels,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const { data: agentsResponse, isLoading: agentsLoading } = useAgents();
|
||||
const agents = agentsResponse?.agents || [];
|
||||
const createNewAgentMutation = useCreateNewAgent();
|
||||
|
||||
// Combine all agents
|
||||
const allAgents = [
|
||||
|
@ -120,19 +108,31 @@ export const ChatSettingsDropdown: React.FC<ChatSettingsDropdownProps> = ({
|
|||
|
||||
const getAgentDisplay = () => {
|
||||
const selectedAgent = allAgents.find(agent => agent.id === selectedAgentId);
|
||||
|
||||
if (selectedAgent) {
|
||||
console.log('Selected agent found:', selectedAgent.name, 'with ID:', selectedAgent.id);
|
||||
return {
|
||||
name: selectedAgent.name,
|
||||
icon: selectedAgent.icon
|
||||
};
|
||||
}
|
||||
|
||||
// If selectedAgentId is not undefined but no agent is found, log a warning
|
||||
if (selectedAgentId !== undefined) {
|
||||
console.warn('Agent with ID', selectedAgentId, 'not found, falling back to Suna');
|
||||
}
|
||||
|
||||
// Default to Suna (the first agent which has id: undefined)
|
||||
const defaultAgent = allAgents[0];
|
||||
console.log('Using default agent:', defaultAgent.name);
|
||||
return {
|
||||
name: 'Suna',
|
||||
icon: <Image src="/kortix-symbol.svg" alt="Suna" width={16} height={16} className="h-4 w-4 dark:invert" />
|
||||
name: defaultAgent.name,
|
||||
icon: defaultAgent.icon
|
||||
};
|
||||
};
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
console.log('Agent selected:', agentId === undefined ? 'Suna (default)' : agentId);
|
||||
onAgentSelect?.(agentId);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
@ -169,9 +169,9 @@ export const ChatSettingsDropdown: React.FC<ChatSettingsDropdownProps> = ({
|
|||
router.push('/agents');
|
||||
};
|
||||
|
||||
const handleMoreOptions = () => {
|
||||
const handleCreateAgent = () => {
|
||||
setIsOpen(false);
|
||||
setDialogOpen(true);
|
||||
createNewAgentMutation.mutate();
|
||||
};
|
||||
|
||||
const renderAgentItem = (agent: any, index: number) => {
|
||||
|
@ -325,42 +325,33 @@ export const ChatSettingsDropdown: React.FC<ChatSettingsDropdownProps> = ({
|
|||
|
||||
{/* Footer Actions */}
|
||||
<div className="p-4 pt-3 border-t border-border/40">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleExploreAll}
|
||||
className="text-xs flex items-center gap-1.5 rounded-lg hover:bg-accent/30 transition-colors duration-200 border-border/50"
|
||||
className="text-xs flex items-center gap-2 rounded-xl hover:bg-accent/40 transition-all duration-200 text-muted-foreground hover:text-foreground px-4 py-2"
|
||||
>
|
||||
<Search className="h-3 w-3" />
|
||||
Explore All
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
Explore All Agents
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-4 bg-border/60" />
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleMoreOptions}
|
||||
className="text-xs flex items-center gap-1.5 rounded-lg hover:bg-accent/30 transition-colors duration-200 border-border/50"
|
||||
onClick={handleCreateAgent}
|
||||
className="text-xs flex items-center gap-2 rounded-xl hover:bg-accent/40 transition-all duration-200 text-muted-foreground hover:text-foreground px-4 py-2"
|
||||
>
|
||||
<Settings className="h-3 w-3" />
|
||||
More Options
|
||||
<ChevronRight className="h-2.5 w-2.5" />
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Create Agent
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<ChatSettingsDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -121,14 +121,54 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
|
|||
const deleteFileMutation = useFileDelete();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const hasLoadedFromLocalStorage = useRef(false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getPendingFiles: () => pendingFiles,
|
||||
clearPendingFiles: () => setPendingFiles([]),
|
||||
}));
|
||||
|
||||
// Load saved agent from localStorage on mount
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && onAgentSelect && !hasLoadedFromLocalStorage.current) {
|
||||
// Don't load from localStorage if an agent is already selected
|
||||
// or if there are URL parameters that might be setting the agent
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const hasAgentIdInUrl = urlParams.has('agent_id');
|
||||
|
||||
if (!selectedAgentId && !hasAgentIdInUrl) {
|
||||
const savedAgentId = localStorage.getItem('lastSelectedAgentId');
|
||||
if (savedAgentId) {
|
||||
// Convert 'suna' back to undefined for the default agent
|
||||
const agentIdToSelect = savedAgentId === 'suna' ? undefined : savedAgentId;
|
||||
console.log('Loading saved agent from localStorage:', savedAgentId);
|
||||
onAgentSelect(agentIdToSelect);
|
||||
} else {
|
||||
console.log('No saved agent found in localStorage');
|
||||
}
|
||||
} else {
|
||||
console.log('Skipping localStorage load:', {
|
||||
hasSelectedAgent: !!selectedAgentId,
|
||||
hasAgentIdInUrl,
|
||||
selectedAgentId
|
||||
});
|
||||
}
|
||||
hasLoadedFromLocalStorage.current = true;
|
||||
}
|
||||
}, [onAgentSelect, selectedAgentId]); // Keep selectedAgentId to check current state
|
||||
|
||||
// Save selected agent to localStorage whenever it changes
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Use 'suna' as a special key for the default agent (undefined)
|
||||
const keyToStore = selectedAgentId === undefined ? 'suna' : selectedAgentId;
|
||||
console.log('Saving selected agent to localStorage:', keyToStore);
|
||||
localStorage.setItem('lastSelectedAgentId', keyToStore);
|
||||
}
|
||||
}, [selectedAgentId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus && textareaRef.current) {
|
||||
textareaRef.current.focus();
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Settings, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ModelSelector } from './model-selector';
|
||||
import { SubscriptionStatus } from './_use-model-selection';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { BillingModal } from '@/components/billing/billing-modal';
|
||||
|
||||
interface ChatSettingsDialogProps {
|
||||
selectedModel: string;
|
||||
onModelChange: (model: string) => void;
|
||||
modelOptions: any[];
|
||||
subscriptionStatus: SubscriptionStatus;
|
||||
canAccessModel: (modelId: string) => boolean;
|
||||
refreshCustomModels?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function ChatSettingsDialog({
|
||||
selectedModel,
|
||||
onModelChange,
|
||||
modelOptions,
|
||||
subscriptionStatus,
|
||||
canAccessModel,
|
||||
refreshCustomModels,
|
||||
disabled = false,
|
||||
className,
|
||||
open: controlledOpen,
|
||||
onOpenChange: controlledOnOpenChange,
|
||||
}: ChatSettingsDialogProps) {
|
||||
const [internalOpen, setInternalOpen] = useState(false);
|
||||
const [billingModalOpen, setBillingModalOpen] = useState(false);
|
||||
|
||||
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
||||
const setOpen = controlledOnOpenChange || setInternalOpen;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{controlledOpen === undefined && (
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
'h-8 w-8 p-0 text-muted-foreground hover:text-foreground',
|
||||
'rounded-lg',
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
)}
|
||||
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
Chat Settings
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model-selector" className="text-sm font-medium">
|
||||
AI Model
|
||||
</Label>
|
||||
<div className="w-full">
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
hasBorder={true}
|
||||
billingModalOpen={billingModalOpen}
|
||||
setBillingModalOpen={setBillingModalOpen}
|
||||
/>
|
||||
</div>
|
||||
{/* Billing Modal */}
|
||||
<BillingModal
|
||||
open={billingModalOpen}
|
||||
onOpenChange={setBillingModalOpen}
|
||||
returnUrl={typeof window !== 'undefined' ? window.location.href : '/'}
|
||||
/>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose the AI model that best fits your needs. Premium models offer better performance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -7,7 +7,7 @@ import { UploadedFile } from './chat-input';
|
|||
import { FileUploadHandler } from './file-upload-handler';
|
||||
import { VoiceRecorder } from './voice-recorder';
|
||||
import { ModelSelector } from './model-selector';
|
||||
import { ChatSettingsDropdown } from './chat-settings-dropdown';
|
||||
import { AgentSelector } from './agent-selector';
|
||||
import { canAccessModel, SubscriptionStatus } from './_use-model-selection';
|
||||
import { isLocalMode } from '@/lib/config';
|
||||
import { useFeatureFlag } from '@/lib/feature-flags';
|
||||
|
@ -131,41 +131,29 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
|||
|
||||
const renderDropdown = () => {
|
||||
if (isLoggedIn) {
|
||||
if (hideAgentSelection) {
|
||||
return <ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
billingModalOpen={billingModalOpen}
|
||||
setBillingModalOpen={setBillingModalOpen}
|
||||
/>
|
||||
} else if (enableAdvancedConfig || (customAgentsEnabled && !flagsLoading)) {
|
||||
return <ChatSettingsDropdown
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
disabled={loading || (disabled && !isAgentRunning)}
|
||||
/>
|
||||
} else {
|
||||
return <ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
billingModalOpen={billingModalOpen}
|
||||
setBillingModalOpen={setBillingModalOpen}
|
||||
/>
|
||||
}
|
||||
const showAdvancedFeatures = enableAdvancedConfig || (customAgentsEnabled && !flagsLoading);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{showAdvancedFeatures && !hideAgentSelection && (
|
||||
<AgentSelector
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
disabled={loading || (disabled && !isAgentRunning)}
|
||||
/>
|
||||
)}
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
billingModalOpen={billingModalOpen}
|
||||
setBillingModalOpen={setBillingModalOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <ChatDropdown />;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Check, ChevronDown, Search, AlertTriangle, Crown, ArrowUpRight, Brain, Plus, Edit, Trash } from 'lucide-react';
|
||||
import { Check, ChevronDown, Search, AlertTriangle, Crown, ArrowUpRight, Brain, Plus, Edit, Trash, Cpu } from 'lucide-react';
|
||||
import {
|
||||
ModelOption,
|
||||
SubscriptionStatus,
|
||||
|
@ -91,9 +91,6 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||
}
|
||||
}, [customModels]);
|
||||
|
||||
// Get current custom models from state
|
||||
const currentCustomModels = customModels || [];
|
||||
|
||||
// Enhance model options with capabilities - using a Map to ensure uniqueness
|
||||
const modelMap = new Map();
|
||||
|
||||
|
@ -511,30 +508,29 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||
return (
|
||||
<div className="relative">
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant={hasBorder ? "outline" : "ghost"}
|
||||
size="default"
|
||||
className="h-8 rounded-lg text-muted-foreground shadow-none border-none focus:ring-0 px-3"
|
||||
>
|
||||
<div className="flex items-center gap-1 text-sm font-medium">
|
||||
{MODELS[selectedModel]?.lowQuality && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mr-1" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="text-xs">
|
||||
<p>Basic model with limited capabilities</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<span className="truncate max-w-[100px] sm:max-w-[160px] md:max-w-[200px] lg:max-w-none">{selectedLabel}</span>
|
||||
<ChevronDown className="h-3 w-3 opacity-50 ml-1 flex-shrink-0" />
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2 py-2 bg-transparent border-0 rounded-xl text-muted-foreground hover:text-foreground hover:bg-accent/50 flex items-center gap-2"
|
||||
>
|
||||
<div className="relative flex items-center justify-center">
|
||||
<Cpu className="h-4 w-4" />
|
||||
{MODELS[selectedModel]?.lowQuality && (
|
||||
<AlertTriangle className="h-2.5 w-2.5 text-amber-500 absolute -top-1 -right-1" />
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="text-xs">
|
||||
<p>Choose a model</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Mic, Square, Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useTranscription } from '@/hooks/react-query/transcription/use-transcription';
|
||||
|
||||
interface VoiceRecorderProps {
|
||||
|
@ -136,11 +142,11 @@ export const VoiceRecorder: React.FC<VoiceRecorderProps> = ({
|
|||
const getButtonClass = () => {
|
||||
switch (state) {
|
||||
case 'recording':
|
||||
return 'text-red-500 hover:bg-red-600';
|
||||
return 'text-red-500 hover:bg-red-50 hover:text-red-600';
|
||||
case 'processing':
|
||||
return 'hover:bg-gray-100';
|
||||
return '';
|
||||
default:
|
||||
return 'hover:bg-gray-100';
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -156,17 +162,32 @@ export const VoiceRecorder: React.FC<VoiceRecorderProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleRightClick}
|
||||
disabled={disabled || state === 'processing'}
|
||||
className={`h-8 w-8 p-0 transition-colors ${getButtonClass()}`}
|
||||
title={state === 'recording' ? 'Click to stop' : 'Click to start recording'}
|
||||
>
|
||||
{getIcon()}
|
||||
</Button>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleRightClick}
|
||||
disabled={disabled || state === 'processing'}
|
||||
className={`h-8 px-2 py-2 bg-transparent border-0 rounded-xl text-muted-foreground hover:text-foreground hover:bg-accent/50 flex items-center gap-2 transition-colors ${getButtonClass()}`}
|
||||
>
|
||||
{getIcon()}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="text-xs">
|
||||
<p>
|
||||
{state === 'recording'
|
||||
? 'Click to stop recording'
|
||||
: state === 'processing'
|
||||
? 'Processing...'
|
||||
: 'Record voice message'
|
||||
}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
|
@ -4,6 +4,9 @@ import { toast } from 'sonner';
|
|||
import { agentKeys } from './keys';
|
||||
import { Agent, AgentUpdateRequest, AgentsParams, createAgent, deleteAgent, getAgent, getAgents, getThreadAgent, updateAgent, AgentBuilderChatRequest, AgentBuilderStreamData, startAgentBuilderChat, getAgentBuilderChatHistory } from './utils';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { generateRandomAvatar } from '@/lib/utils/_avatar-generator';
|
||||
import { DEFAULT_AGENTPRESS_TOOLS } from '@/components/agents/tools';
|
||||
|
||||
export const useAgents = (params: AgentsParams = {}) => {
|
||||
return createQueryHook(
|
||||
|
@ -44,6 +47,45 @@ export const useCreateAgent = () => {
|
|||
)();
|
||||
};
|
||||
|
||||
export const useCreateNewAgent = () => {
|
||||
const router = useRouter();
|
||||
const createAgentMutation = useCreateAgent();
|
||||
|
||||
return createMutationHook(
|
||||
async (_: void) => {
|
||||
const { avatar, avatar_color } = generateRandomAvatar();
|
||||
|
||||
const defaultAgentData = {
|
||||
name: 'New Agent',
|
||||
description: '',
|
||||
system_prompt: 'You are a helpful assistant. Provide clear, accurate, and helpful responses to user queries.',
|
||||
avatar,
|
||||
avatar_color,
|
||||
configured_mcps: [],
|
||||
agentpress_tools: Object.fromEntries(
|
||||
Object.entries(DEFAULT_AGENTPRESS_TOOLS).map(([key, value]) => [
|
||||
key,
|
||||
{ enabled: value.enabled, description: value.description }
|
||||
])
|
||||
),
|
||||
is_default: false,
|
||||
};
|
||||
|
||||
const newAgent = await createAgentMutation.mutateAsync(defaultAgentData);
|
||||
return newAgent;
|
||||
},
|
||||
{
|
||||
onSuccess: (newAgent) => {
|
||||
router.push(`/agents/config/${newAgent.agent_id}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error creating agent:', error);
|
||||
toast.error('Failed to create agent. Please try again.');
|
||||
},
|
||||
}
|
||||
)();
|
||||
};
|
||||
|
||||
export const useUpdateAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
|
Loading…
Reference in New Issue