mirror of https://github.com/kortix-ai/suna.git
Merge pull request #1788 from escapade-mckv/triggers-display
Triggers display
This commit is contained in:
commit
2b22aed8b0
|
@ -50,6 +50,7 @@ import { useAgentVersionData } from '@/hooks/use-agent-version-data';
|
|||
import { useUpdateAgent, useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
|
||||
import { useExportAgent } from '@/hooks/react-query/agents/use-agent-export-import';
|
||||
import { agentKeys } from '@/hooks/react-query/agents/keys';
|
||||
import { ExpandableMarkdownEditor } from '@/components/ui/expandable-markdown-editor';
|
||||
import { AgentModelSelector } from './config/model-selector';
|
||||
import { AgentToolsConfiguration } from './agent-tools-configuration';
|
||||
|
@ -82,7 +83,11 @@ export function AgentConfigurationDialog({
|
|||
const queryClient = useQueryClient();
|
||||
|
||||
const { agent, versionData, isViewingOldVersion, isLoading, error } = useAgentVersionData({ agentId });
|
||||
const { data: agentsResponse } = useAgents({}, { enabled: !!onAgentChange });
|
||||
const { data: agentsResponse, refetch: refetchAgents } = useAgents({}, {
|
||||
enabled: !!onAgentChange,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnMount: 'always'
|
||||
});
|
||||
const agents = agentsResponse?.agents || [];
|
||||
|
||||
const updateAgentMutation = useUpdateAgent();
|
||||
|
@ -106,6 +111,29 @@ export function AgentConfigurationDialog({
|
|||
}
|
||||
}, [open, initialTab]);
|
||||
|
||||
// Listen for query invalidations and refetch when agent data changes
|
||||
useEffect(() => {
|
||||
const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
|
||||
// Check if the invalidation is for this agent's data
|
||||
if (event?.type === 'updated' && event?.query?.queryKey) {
|
||||
const queryKey = event.query.queryKey;
|
||||
|
||||
// Check if it's an agent-related query for our agentId
|
||||
if (
|
||||
(Array.isArray(queryKey) && queryKey.includes(agentId)) ||
|
||||
(Array.isArray(queryKey) && queryKey.includes('agents') && queryKey.includes('detail')) ||
|
||||
(Array.isArray(queryKey) && queryKey.includes('versions'))
|
||||
) {
|
||||
// Force a re-render by invalidating our specific queries
|
||||
queryClient.invalidateQueries({ queryKey: agentKeys.detail(agentId) });
|
||||
queryClient.invalidateQueries({ queryKey: ['versions', 'list', agentId] });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, [agentId, queryClient]);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
system_prompt: '',
|
||||
|
|
|
@ -167,11 +167,9 @@ export function CustomAgentsSection({ onAgentSelect }: CustomAgentsSectionProps)
|
|||
<TitleSection />
|
||||
<div className="grid gap-4 pb-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
|
||||
<div key={i} className="bg-muted/30 rounded-3xl p-4 h-[180px] w-full">
|
||||
<Skeleton className="h-12 w-12 rounded-2xl mb-3" />
|
||||
<Skeleton className="h-5 w-3/4 mb-2" />
|
||||
<Skeleton className="h-10 w-full mb-3" />
|
||||
<Skeleton className="h-8 w-full mt-auto" />
|
||||
<div key={i} className="bg-muted/30 rounded-3xl p-4 h-auto w-full flex items-center">
|
||||
<Skeleton className="w-12 h-12 rounded-xl" />
|
||||
<Skeleton className="h-6 w-1/2 ml-4" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import { parseToolResult } from '../tool-result-parser';
|
||||
|
||||
export interface McpTool {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema?: any;
|
||||
}
|
||||
|
||||
export interface ProfileInfo {
|
||||
profile_name: string;
|
||||
toolkit_name: string;
|
||||
toolkit_slug: string;
|
||||
is_connected: boolean;
|
||||
}
|
||||
|
||||
export interface DiscoverUserMcpServersData {
|
||||
profile_id: string | null;
|
||||
message: string | null;
|
||||
profile_info: ProfileInfo | null;
|
||||
tools: McpTool[];
|
||||
total_tools: number;
|
||||
success?: boolean;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
const parseContent = (content: any): any => {
|
||||
if (typeof content === 'string') {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export function extractDiscoverUserMcpServersData(
|
||||
assistantContent?: string,
|
||||
toolContent?: any,
|
||||
isSuccess?: boolean,
|
||||
toolTimestamp?: string,
|
||||
assistantTimestamp?: string
|
||||
): DiscoverUserMcpServersData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} {
|
||||
const defaultResult: DiscoverUserMcpServersData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} = {
|
||||
profile_id: null,
|
||||
message: null,
|
||||
profile_info: null,
|
||||
tools: [],
|
||||
total_tools: 0,
|
||||
actualIsSuccess: isSuccess || false,
|
||||
actualToolTimestamp: toolTimestamp,
|
||||
actualAssistantTimestamp: assistantTimestamp
|
||||
};
|
||||
|
||||
try {
|
||||
if (toolContent) {
|
||||
let content = toolContent;
|
||||
|
||||
if (typeof toolContent === 'string') {
|
||||
try {
|
||||
content = JSON.parse(toolContent);
|
||||
} catch (e) {
|
||||
content = toolContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.content) {
|
||||
try {
|
||||
const nestedContent = typeof content.content === 'string' ? JSON.parse(content.content) : content.content;
|
||||
content = nestedContent;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool_execution) {
|
||||
const toolExecution = content.tool_execution;
|
||||
if (toolExecution.result && toolExecution.result.success) {
|
||||
const args = toolExecution.arguments;
|
||||
const output = toolExecution.result.output;
|
||||
|
||||
if (args && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
profile_id: args.profile_id || null,
|
||||
message: output.message || null,
|
||||
profile_info: output.profile_info || null,
|
||||
tools: output.tools || [],
|
||||
total_tools: output.total_tools || 0,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool === 'discover-user-mcp-servers') {
|
||||
const parameters = content.parameters;
|
||||
const output = content.output;
|
||||
|
||||
if (parameters && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
profile_id: parameters.profile_id || null,
|
||||
message: output.message || null,
|
||||
profile_info: output.profile_info || null,
|
||||
tools: output.tools || [],
|
||||
total_tools: output.total_tools || 0,
|
||||
actualIsSuccess: output.success !== false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (assistantContent) {
|
||||
const parsed = parseToolResult(assistantContent);
|
||||
if (parsed && parsed.isSuccess) {
|
||||
const toolOutput = parseContent(parsed.toolOutput);
|
||||
const args = parsed.arguments;
|
||||
|
||||
if (args && toolOutput) {
|
||||
return {
|
||||
...defaultResult,
|
||||
profile_id: args.profile_id || null,
|
||||
message: toolOutput.message || null,
|
||||
profile_info: toolOutput.profile_info || null,
|
||||
tools: toolOutput.tools || [],
|
||||
total_tools: toolOutput.total_tools || 0,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultResult;
|
||||
} catch (error) {
|
||||
console.error('Error extracting discover user mcp servers data:', error);
|
||||
return defaultResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Search,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Plug,
|
||||
Zap,
|
||||
Package,
|
||||
Link2,
|
||||
Wrench,
|
||||
ChevronRight,
|
||||
Database
|
||||
} from 'lucide-react';
|
||||
import { ToolViewProps } from '../types';
|
||||
import { formatTimestamp, getToolTitle } from '../utils';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { LoadingState } from '../shared/LoadingState';
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { extractDiscoverUserMcpServersData, McpTool } from './_utils';
|
||||
|
||||
export function DiscoverUserMcpServersToolView({
|
||||
name = 'discover-user-mcp-servers',
|
||||
assistantContent,
|
||||
toolContent,
|
||||
assistantTimestamp,
|
||||
toolTimestamp,
|
||||
isSuccess = true,
|
||||
isStreaming = false,
|
||||
}: ToolViewProps) {
|
||||
|
||||
const {
|
||||
profile_id,
|
||||
message,
|
||||
profile_info,
|
||||
tools,
|
||||
total_tools,
|
||||
actualIsSuccess,
|
||||
actualToolTimestamp,
|
||||
actualAssistantTimestamp
|
||||
} = extractDiscoverUserMcpServersData(
|
||||
assistantContent,
|
||||
toolContent,
|
||||
isSuccess,
|
||||
toolTimestamp,
|
||||
assistantTimestamp
|
||||
);
|
||||
|
||||
const toolTitle = getToolTitle(name);
|
||||
|
||||
const formatToolName = (toolName: string): string => {
|
||||
return toolName
|
||||
.replace(/^LINEAR_/, '')
|
||||
.replace(/_/g, ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0) + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const getToolCategory = (toolName: string): string => {
|
||||
if (toolName.includes('CREATE')) return 'Create';
|
||||
if (toolName.includes('UPDATE')) return 'Update';
|
||||
if (toolName.includes('DELETE') || toolName.includes('REMOVE')) return 'Delete';
|
||||
if (toolName.includes('GET') || toolName.includes('LIST')) return 'Read';
|
||||
if (toolName.includes('RUN')) return 'Advanced';
|
||||
return 'Other';
|
||||
};
|
||||
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category) {
|
||||
case 'Create':
|
||||
return <span className="text-green-500">+</span>;
|
||||
case 'Update':
|
||||
return <span className="text-blue-500">✎</span>;
|
||||
case 'Delete':
|
||||
return <span className="text-red-500">×</span>;
|
||||
case 'Read':
|
||||
return <span className="text-purple-500">⊙</span>;
|
||||
case 'Advanced':
|
||||
return <span className="text-orange-500">⚡</span>;
|
||||
default:
|
||||
return <span className="text-gray-500">•</span>;
|
||||
}
|
||||
};
|
||||
|
||||
const groupToolsByCategory = (tools: McpTool[]) => {
|
||||
const grouped: Record<string, McpTool[]> = {};
|
||||
tools.forEach(tool => {
|
||||
const category = getToolCategory(tool.name);
|
||||
if (!grouped[category]) {
|
||||
grouped[category] = [];
|
||||
}
|
||||
grouped[category].push(tool);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
|
||||
const groupedTools = groupToolsByCategory(tools);
|
||||
const categoryOrder = ['Read', 'Create', 'Update', 'Delete', 'Advanced', 'Other'];
|
||||
|
||||
return (
|
||||
<Card className="gap-0 flex border shadow-none border-t border-b-0 border-x-0 p-0 rounded-none flex-col h-full overflow-hidden bg-card">
|
||||
<CardHeader className="h-14 bg-zinc-50/80 dark:bg-zinc-900/80 backdrop-blur-sm border-b p-2 px-4 space-y-2">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative p-2 rounded-xl bg-gradient-to-br from-purple-500/20 to-purple-600/10 border border-purple-500/20">
|
||||
<Search className="w-5 h-5 text-purple-500 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{toolTitle}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
{!isStreaming && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
actualIsSuccess
|
||||
? "bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/20 dark:text-emerald-300 dark:border-emerald-800"
|
||||
: "bg-red-50 text-red-700 border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-800"
|
||||
)}
|
||||
>
|
||||
{actualIsSuccess ? (
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
) : (
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
)}
|
||||
{actualIsSuccess ? 'Tools discovered' : 'Discovery failed'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0 h-full flex-1 overflow-hidden relative">
|
||||
{isStreaming ? (
|
||||
<LoadingState
|
||||
icon={Search}
|
||||
iconColor="text-purple-500 dark:text-purple-400"
|
||||
bgColor="bg-gradient-to-b from-purple-100 to-purple-50 shadow-inner dark:from-purple-800/40 dark:to-purple-900/60 dark:shadow-purple-950/20"
|
||||
title="Discovering MCP tools"
|
||||
showProgress={true}
|
||||
/>
|
||||
) : actualIsSuccess && profile_info ? (
|
||||
<ScrollArea className="h-full w-full">
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="border rounded-xl p-4 space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-100 to-purple-50 dark:from-purple-900/40 dark:to-purple-800/20 border border-purple-200 dark:border-purple-800 flex items-center justify-center">
|
||||
<Plug className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">
|
||||
{profile_info.profile_name}
|
||||
</h3>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{profile_info.toolkit_name} Integration
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={profile_info.is_connected ? "default" : "secondary"}
|
||||
className={cn(
|
||||
"text-xs",
|
||||
profile_info.is_connected
|
||||
? "bg-green-100 text-green-700 border-green-200 dark:bg-green-900/20 dark:text-green-300 dark:border-green-800"
|
||||
: "bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-900/20 dark:text-gray-300 dark:border-gray-800"
|
||||
)}
|
||||
>
|
||||
{profile_info.is_connected ? (
|
||||
<>
|
||||
<Link2 className="h-3 w-3 mr-1" />
|
||||
Connected
|
||||
</>
|
||||
) : 'Disconnected'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tools.length > 0 && (
|
||||
<div className="border rounded-xl p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Wrench className="w-4 h-4 text-zinc-600 dark:text-zinc-400" />
|
||||
<h4 className="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
Discovered Tools
|
||||
</h4>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{total_tools} available
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
{categoryOrder.map(category => {
|
||||
if (!groupedTools[category] || groupedTools[category].length === 0) return null;
|
||||
|
||||
return (
|
||||
<div key={category} className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||
<span className="w-5 h-5 flex items-center justify-center">
|
||||
{getCategoryIcon(category)}
|
||||
</span>
|
||||
<span>{category} Operations</span>
|
||||
<Badge variant="secondary" className="text-xs ml-auto">
|
||||
{groupedTools[category].length}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pl-7">
|
||||
{groupedTools[category].map((tool, index) => (
|
||||
<div key={index} className="border rounded-lg p-3 space-y-2 bg-zinc-50/50 dark:bg-zinc-800/30">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="w-3 h-3 text-purple-500 dark:text-purple-400" />
|
||||
<p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{formatToolName(tool.name)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-zinc-600 dark:text-zinc-400 pl-5">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tools.length === 0 && (
|
||||
<div className="border rounded-xl p-6 text-center">
|
||||
<Package className="w-12 h-12 mx-auto text-zinc-400 dark:text-zinc-600 mb-3" />
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
No tools discovered for this profile
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<div className="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg m-4">
|
||||
<p className="text-sm text-red-800 dark:text-red-200 flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Failed to discover MCP tools. Please check the profile configuration.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
import { parseToolResult } from '../tool-result-parser';
|
||||
|
||||
export interface UpdateAgentData {
|
||||
name: string | null;
|
||||
description?: string | null;
|
||||
system_prompt: string | null;
|
||||
agentpress_tools: Record<string, boolean> | null;
|
||||
configured_mcps?: any[] | null;
|
||||
is_default?: boolean;
|
||||
icon_name?: string | null;
|
||||
icon_color?: string | null;
|
||||
icon_background?: string | null;
|
||||
agent?: {
|
||||
agent_id: string;
|
||||
account_id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
is_default: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
is_public: boolean;
|
||||
tags: string[];
|
||||
current_version_id: string;
|
||||
version_count: number;
|
||||
metadata: Record<string, any>;
|
||||
icon_name: string;
|
||||
icon_color: string;
|
||||
icon_background: string;
|
||||
} | null;
|
||||
updated_fields?: string[];
|
||||
version_created?: boolean;
|
||||
message?: string;
|
||||
success?: boolean;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
const parseContent = (content: any): any => {
|
||||
if (typeof content === 'string') {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
export function extractUpdateAgentData(
|
||||
assistantContent?: string,
|
||||
toolContent?: any,
|
||||
isSuccess?: boolean,
|
||||
toolTimestamp?: string,
|
||||
assistantTimestamp?: string
|
||||
): UpdateAgentData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} {
|
||||
const defaultResult: UpdateAgentData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} = {
|
||||
name: null,
|
||||
description: null,
|
||||
system_prompt: null,
|
||||
agentpress_tools: null,
|
||||
configured_mcps: null,
|
||||
is_default: false,
|
||||
icon_name: null,
|
||||
icon_color: null,
|
||||
icon_background: null,
|
||||
agent: null,
|
||||
updated_fields: [],
|
||||
version_created: false,
|
||||
message: null,
|
||||
actualIsSuccess: isSuccess || false,
|
||||
actualToolTimestamp: toolTimestamp,
|
||||
actualAssistantTimestamp: assistantTimestamp
|
||||
};
|
||||
|
||||
try {
|
||||
if (toolContent) {
|
||||
let content = toolContent;
|
||||
|
||||
if (typeof toolContent === 'string') {
|
||||
try {
|
||||
content = JSON.parse(toolContent);
|
||||
} catch (e) {
|
||||
content = toolContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.content) {
|
||||
try {
|
||||
const nestedContent = typeof content.content === 'string' ? JSON.parse(content.content) : content.content;
|
||||
content = nestedContent;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool_execution) {
|
||||
const toolExecution = content.tool_execution;
|
||||
if (toolExecution.result && toolExecution.result.success) {
|
||||
const args = toolExecution.arguments;
|
||||
const output = toolExecution.result.output;
|
||||
|
||||
if (args && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
name: args.name || null,
|
||||
description: args.description || null,
|
||||
system_prompt: args.system_prompt || null,
|
||||
agentpress_tools: args.agentpress_tools || null,
|
||||
configured_mcps: args.configured_mcps || null,
|
||||
is_default: args.is_default || false,
|
||||
icon_name: args.icon_name || null,
|
||||
icon_color: args.icon_color || null,
|
||||
icon_background: args.icon_background || null,
|
||||
agent: output.agent || null,
|
||||
updated_fields: output.updated_fields || [],
|
||||
version_created: output.version_created || false,
|
||||
message: output.message || null,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool === 'update-agent') {
|
||||
const parameters = content.parameters;
|
||||
const output = content.output;
|
||||
|
||||
if (parameters && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
name: parameters.name || null,
|
||||
description: parameters.description || null,
|
||||
system_prompt: parameters.system_prompt || null,
|
||||
agentpress_tools: parameters.agentpress_tools || null,
|
||||
configured_mcps: parameters.configured_mcps || null,
|
||||
is_default: parameters.is_default || false,
|
||||
icon_name: parameters.icon_name || null,
|
||||
icon_color: parameters.icon_color || null,
|
||||
icon_background: parameters.icon_background || null,
|
||||
agent: output.agent || null,
|
||||
updated_fields: output.updated_fields || [],
|
||||
version_created: output.version_created || false,
|
||||
message: output.message || null,
|
||||
actualIsSuccess: output.success !== false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (assistantContent) {
|
||||
const parsed = parseToolResult(assistantContent);
|
||||
if (parsed && parsed.isSuccess) {
|
||||
const toolOutput = parseContent(parsed.toolOutput);
|
||||
const args = parsed.arguments;
|
||||
|
||||
if (args && toolOutput) {
|
||||
return {
|
||||
...defaultResult,
|
||||
name: args.name || null,
|
||||
description: args.description || null,
|
||||
system_prompt: args.system_prompt || null,
|
||||
agentpress_tools: args.agentpress_tools || null,
|
||||
configured_mcps: args.configured_mcps || null,
|
||||
is_default: args.is_default || false,
|
||||
icon_name: args.icon_name || null,
|
||||
icon_color: args.icon_color || null,
|
||||
icon_background: args.icon_background || null,
|
||||
agent: toolOutput.agent || null,
|
||||
updated_fields: toolOutput.updated_fields || [],
|
||||
version_created: toolOutput.version_created || false,
|
||||
message: toolOutput.message || null,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultResult;
|
||||
} catch (error) {
|
||||
console.error('Error extracting update agent data:', error);
|
||||
return defaultResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Bot,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Calendar,
|
||||
Sparkles,
|
||||
User,
|
||||
RefreshCw,
|
||||
History,
|
||||
Edit3
|
||||
} from 'lucide-react';
|
||||
import { ToolViewProps } from '../types';
|
||||
import { formatTimestamp, getToolTitle } from '../utils';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { LoadingState } from '../shared/LoadingState';
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { extractUpdateAgentData } from './_utils';
|
||||
import { AgentAvatar } from '../../content/agent-avatar';
|
||||
|
||||
export function UpdateAgentToolView({
|
||||
name = 'update-agent',
|
||||
assistantContent,
|
||||
toolContent,
|
||||
assistantTimestamp,
|
||||
toolTimestamp,
|
||||
isSuccess = true,
|
||||
isStreaming = false,
|
||||
}: ToolViewProps) {
|
||||
|
||||
const {
|
||||
name: agentName,
|
||||
description,
|
||||
system_prompt,
|
||||
icon_name,
|
||||
icon_color,
|
||||
icon_background,
|
||||
agentpress_tools,
|
||||
configured_mcps,
|
||||
is_default,
|
||||
agent,
|
||||
updated_fields,
|
||||
version_created,
|
||||
message,
|
||||
actualIsSuccess,
|
||||
actualToolTimestamp,
|
||||
actualAssistantTimestamp
|
||||
} = extractUpdateAgentData(
|
||||
assistantContent,
|
||||
toolContent,
|
||||
isSuccess,
|
||||
toolTimestamp,
|
||||
assistantTimestamp
|
||||
);
|
||||
|
||||
const toolTitle = getToolTitle(name);
|
||||
|
||||
const getEnabledToolsCount = () => {
|
||||
if (!agentpress_tools) return 0;
|
||||
return Object.values(agentpress_tools).filter(Boolean).length;
|
||||
};
|
||||
|
||||
const formatFieldName = (field: string): string => {
|
||||
return field
|
||||
.split('_')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const displayName = agent?.name || agentName;
|
||||
const displayIconName = agent?.icon_name || icon_name;
|
||||
const displayIconColor = agent?.icon_color || icon_color;
|
||||
const displayIconBackground = agent?.icon_background || icon_background;
|
||||
|
||||
return (
|
||||
<Card className="gap-0 flex border shadow-none border-t border-b-0 border-x-0 p-0 rounded-none flex-col h-full overflow-hidden bg-card">
|
||||
<CardHeader className="h-14 bg-zinc-50/80 dark:bg-zinc-900/80 backdrop-blur-sm border-b p-2 px-4 space-y-2">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative p-2 rounded-xl bg-gradient-to-br from-blue-500/20 to-blue-600/10 border border-blue-500/20">
|
||||
<RefreshCw className="w-5 h-5 text-blue-500 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{toolTitle}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
{!isStreaming && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
actualIsSuccess
|
||||
? "bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/20 dark:text-emerald-300 dark:border-emerald-800"
|
||||
: "bg-red-50 text-red-700 border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-800"
|
||||
)}
|
||||
>
|
||||
{actualIsSuccess ? (
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
) : (
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
)}
|
||||
{actualIsSuccess ? 'Agent updated' : 'Update failed'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0 h-full flex-1 overflow-hidden relative">
|
||||
{isStreaming ? (
|
||||
<LoadingState
|
||||
icon={RefreshCw}
|
||||
iconColor="text-blue-500 dark:text-blue-400"
|
||||
bgColor="bg-gradient-to-b from-blue-100 to-blue-50 shadow-inner dark:from-blue-800/40 dark:to-blue-900/60 dark:shadow-blue-950/20"
|
||||
title="Updating agent"
|
||||
filePath={displayName ? `"${displayName}"` : undefined}
|
||||
showProgress={true}
|
||||
/>
|
||||
) : actualIsSuccess && agent ? (
|
||||
<ScrollArea className="h-full w-full">
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="border rounded-xl p-4 space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AgentAvatar
|
||||
iconName={displayIconName}
|
||||
iconColor={displayIconColor}
|
||||
backgroundColor={displayIconBackground}
|
||||
agentName={displayName}
|
||||
size={48}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{displayName}
|
||||
</h3>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Custom AI Agent
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{version_created && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<History className="w-3 h-3 mr-1" />
|
||||
v{agent.version_count}
|
||||
</Badge>
|
||||
)}
|
||||
{is_default && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Sparkles className="w-3 h-3 mr-1" />
|
||||
Default
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant="secondary" className="text-xs bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/20 dark:text-emerald-300 dark:border-emerald-800">
|
||||
Active
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-zinc-900 dark:text-zinc-100 mb-2">Description</h4>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updated_fields && updated_fields.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
<Edit3 className="w-4 h-4" />
|
||||
Updated Fields
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{updated_fields.map((field, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{formatFieldName(field)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
{version_created && (
|
||||
<div className="text-xs text-zinc-600 dark:text-zinc-400 mt-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<History className="w-3 h-3" />
|
||||
New version created (Version {agent.version_count})
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
|
||||
<Calendar className="w-3 h-3" />
|
||||
<span>Created</span>
|
||||
</div>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 pl-5">
|
||||
{new Date(agent.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
|
||||
<RefreshCw className="w-3 h-3" />
|
||||
<span>Updated</span>
|
||||
</div>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 pl-5">
|
||||
{new Date(agent.updated_at).toLocaleDateString()} at{' '}
|
||||
{new Date(agent.updated_at).toLocaleTimeString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{system_prompt && (
|
||||
<div className="border rounded-xl p-4 space-y-3">
|
||||
<h4 className="text-sm font-medium text-zinc-900 dark:text-zinc-100 flex items-center gap-2">
|
||||
<User className="w-4 h-4" />
|
||||
System Prompt Preview
|
||||
</h4>
|
||||
<div className="bg-muted/50 rounded-lg p-3 text-xs text-zinc-600 dark:text-zinc-400 font-mono max-h-32 overflow-y-auto">
|
||||
{system_prompt.substring(0, 200)}
|
||||
{system_prompt.length > 200 && '...'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{agentpress_tools && (
|
||||
<div className="border rounded-xl p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
Tool Configuration
|
||||
</h4>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{getEnabledToolsCount()} enabled
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
Agent has access to {getEnabledToolsCount()} tools out of {Object.keys(agentpress_tools).length} available tools
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200 flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<div className="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg m-4">
|
||||
<p className="text-sm text-red-800 dark:text-red-200 flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Failed to update agent. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -78,10 +78,12 @@ export function getToolTitle(toolName: string): string {
|
|||
|
||||
// Agent Creation Tools
|
||||
'create-new-agent': 'Create New Agent',
|
||||
'update-agent': 'Update Agent',
|
||||
'search-mcp-servers-for-agent': 'Search MCP Servers for Agent',
|
||||
'get-mcp-server-details': 'Get MCP Server Details',
|
||||
'create-credential-profile-for-agent': 'Create Credential Profile for Agent',
|
||||
'discover-mcp-tools-for-agent': 'Discover MCP Tools for Agent',
|
||||
'discover-user-mcp-servers': 'Discovering tools',
|
||||
'configure-agent-integration': 'Configure Agent Integration',
|
||||
'list-available-integrations': 'List Available Integrations',
|
||||
'create-agent-scheduled-trigger': 'Create Scheduled Trigger',
|
||||
|
@ -1306,6 +1308,10 @@ export function getToolComponent(toolName: string): string {
|
|||
return 'GetCredentialProfilesToolView';
|
||||
case 'get-current-agent-config':
|
||||
return 'GetCurrentAgentConfigToolView';
|
||||
case 'update-agent':
|
||||
return 'UpdateAgentToolView';
|
||||
case 'discover-user-mcp-servers':
|
||||
return 'DiscoverUserMcpServersToolView';
|
||||
|
||||
//Deploy
|
||||
case 'deploy':
|
||||
|
|
|
@ -47,9 +47,11 @@ import { DesignerToolView } from '../designer-tool/DesignerToolView';
|
|||
import { UploadFileToolView } from '../UploadFileToolView';
|
||||
import { DocsToolView, ListDocumentsToolView, DeleteDocumentToolView } from '../docs-tool';
|
||||
import { CreateNewAgentToolView } from '../create-new-agent/create-new-agent';
|
||||
import { UpdateAgentToolView } from '../update-agent/update-agent';
|
||||
import { SearchMcpServersForAgentToolView } from '../search-mcp-servers-for-agent/search-mcp-servers-for-agent';
|
||||
import { CreateCredentialProfileForAgentToolView } from '../create-credential-profile-for-agent/create-credential-profile-for-agent';
|
||||
import { DiscoverMcpToolsForAgentToolView } from '../discover-mcp-tools-for-agent/discover-mcp-tools-for-agent';
|
||||
import { DiscoverUserMcpServersToolView } from '../discover-user-mcp-servers/discover-user-mcp-servers';
|
||||
import { ConfigureAgentIntegrationToolView } from '../configure-agent-integration/configure-agent-integration';
|
||||
import CreateAgentScheduledTriggerToolView from '../create-agent-scheduled-trigger/create-agent-scheduled-trigger';
|
||||
import { createPresentationViewerToolContent, parsePresentationSlidePath } from '../utils/presentation-utils';
|
||||
|
@ -189,9 +191,11 @@ const defaultRegistry: ToolViewRegistryType = {
|
|||
'default': GenericToolView,
|
||||
|
||||
'create-new-agent': CreateNewAgentToolView,
|
||||
'update-agent': UpdateAgentToolView,
|
||||
'search-mcp-servers-for-agent': SearchMcpServersForAgentToolView,
|
||||
'create-credential-profile-for-agent': CreateCredentialProfileForAgentToolView,
|
||||
'discover-mcp-tools-for-agent': DiscoverMcpToolsForAgentToolView,
|
||||
'discover-user-mcp-servers': DiscoverUserMcpServersToolView,
|
||||
'configure-agent-integration': ConfigureAgentIntegrationToolView,
|
||||
'create-agent-scheduled-trigger': CreateAgentScheduledTriggerToolView,
|
||||
};
|
||||
|
|
|
@ -399,10 +399,12 @@ const TOOL_DISPLAY_NAMES = new Map([
|
|||
['update_agent', 'Updating Agent'],
|
||||
['get_current_agent_config', 'Getting Agent Config'],
|
||||
['search_mcp_servers', 'Searching MCP Servers'],
|
||||
['get_mcp_server_tools', 'Getting MCP Server Tools'],
|
||||
['configure_mcp_server', 'Configuring MCP Server'],
|
||||
['get_popular_mcp_servers', 'Getting Popular MCP Servers'],
|
||||
['test_mcp_server_connection', 'Testing MCP Server Connection'],
|
||||
['discover-user-mcp-servers', 'Discovering tools'],
|
||||
['create-credential-profile', 'Creating profile'],
|
||||
['get-credential-profiles', 'Getting profiles'],
|
||||
['configure-profile-for-agent', 'Adding tools to agent'],
|
||||
|
||||
|
||||
['create-new-agent', 'Creating New Agent'],
|
||||
|
|
|
@ -32,6 +32,8 @@ export const useAgent = (agentId: string) => {
|
|||
enabled: !!agentId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
gcTime: 10 * 60 * 1000,
|
||||
refetchOnMount: 'always',
|
||||
refetchOnWindowFocus: true,
|
||||
}
|
||||
)();
|
||||
};
|
||||
|
|
|
@ -263,31 +263,35 @@ export function useAgentStream(
|
|||
});
|
||||
|
||||
if (agentId) {
|
||||
// Core agent data
|
||||
queryClient.invalidateQueries({ queryKey: agentKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: agentKeys.detail(agentId) });
|
||||
queryClient.invalidateQueries({ queryKey: agentKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: agentKeys.details() });
|
||||
|
||||
// Agent tools and integrations
|
||||
queryClient.invalidateQueries({ queryKey: ['agent-tools', agentId] });
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['custom-mcp-tools', agentId],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['agent-tools'] });
|
||||
|
||||
// MCP configurations
|
||||
queryClient.invalidateQueries({ queryKey: ['custom-mcp-tools', agentId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['custom-mcp-tools'] });
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.mcpServers() });
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: composioKeys.profiles.all(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: composioKeys.profiles.credentials(),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.profiles.all() });
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.profiles.credentials() });
|
||||
|
||||
// Triggers
|
||||
queryClient.invalidateQueries({ queryKey: ['triggers', agentId] });
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: knowledgeBaseKeys.agent(agentId),
|
||||
});
|
||||
|
||||
// Invalidate versioning queries for agent config page
|
||||
queryClient.invalidateQueries({ queryKey: ['triggers'] });
|
||||
|
||||
// Knowledge base
|
||||
queryClient.invalidateQueries({ queryKey: knowledgeBaseKeys.agent(agentId) });
|
||||
queryClient.invalidateQueries({ queryKey: knowledgeBaseKeys.all });
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['versions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['versions', 'list'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['versions', 'list', agentId] });
|
||||
// Invalidate current version details if available
|
||||
queryClient.invalidateQueries({ queryKey: ['versions', 'detail'] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['versions', 'detail'],
|
||||
predicate: (query) => {
|
||||
|
@ -295,7 +299,14 @@ export function useAgentStream(
|
|||
}
|
||||
});
|
||||
|
||||
console.log(`[useAgentStream] Invalidated agent queries for refetch instead of page reload - Agent ID: ${agentId}`);
|
||||
// Invalidate any version store cache
|
||||
queryClient.invalidateQueries({ queryKey: ['version-store'] });
|
||||
|
||||
// Force refetch of agent configuration data
|
||||
queryClient.refetchQueries({ queryKey: agentKeys.detail(agentId) });
|
||||
queryClient.refetchQueries({ queryKey: ['versions', 'list', agentId] });
|
||||
|
||||
console.log(`[useAgentStream] Comprehensively invalidated and refetched all agent queries for Agent ID: ${agentId}`);
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
@ -36,6 +36,8 @@ export const useAgentVersions = (agentId: string) => {
|
|||
},
|
||||
enabled: !!agentId,
|
||||
staleTime: 30000,
|
||||
refetchOnMount: 'always',
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -50,6 +52,8 @@ export const useAgentVersion = (agentId: string, versionId: string | null | unde
|
|||
},
|
||||
enabled: !!agentId && !!versionId,
|
||||
staleTime: 30000,
|
||||
refetchOnMount: 'always',
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue