mirror of https://github.com/kortix-ai/suna.git
Merge 3b99797898
into 116050f152
This commit is contained in:
commit
89560ee66e
|
@ -0,0 +1,154 @@
|
|||
import { parseToolResult } from '../tool-result-parser';
|
||||
|
||||
export interface TriggerData {
|
||||
provider: string;
|
||||
slug: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface CreateEventTriggerData {
|
||||
slug: string | null;
|
||||
profile_id: string | null;
|
||||
connected_account_id: string | null;
|
||||
trigger_config: Record<string, any> | null;
|
||||
name: string | null;
|
||||
agent_prompt: string | null;
|
||||
message: string | null;
|
||||
trigger: TriggerData | null;
|
||||
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 extractCreateEventTriggerData(
|
||||
assistantContent?: string,
|
||||
toolContent?: any,
|
||||
isSuccess?: boolean,
|
||||
toolTimestamp?: string,
|
||||
assistantTimestamp?: string
|
||||
): CreateEventTriggerData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} {
|
||||
const defaultResult: CreateEventTriggerData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} = {
|
||||
slug: null,
|
||||
profile_id: null,
|
||||
connected_account_id: null,
|
||||
trigger_config: null,
|
||||
name: null,
|
||||
agent_prompt: null,
|
||||
message: null,
|
||||
trigger: 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,
|
||||
slug: args.slug || null,
|
||||
profile_id: args.profile_id || null,
|
||||
connected_account_id: args.connected_account_id || null,
|
||||
trigger_config: args.trigger_config || null,
|
||||
name: args.name || null,
|
||||
agent_prompt: args.agent_prompt || null,
|
||||
message: output.message || null,
|
||||
trigger: output.trigger || null,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool === 'create-event-trigger') {
|
||||
const parameters = content.parameters;
|
||||
const output = content.output;
|
||||
|
||||
if (parameters && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
slug: parameters.slug || null,
|
||||
profile_id: parameters.profile_id || null,
|
||||
connected_account_id: parameters.connected_account_id || null,
|
||||
trigger_config: parameters.trigger_config || null,
|
||||
name: parameters.name || null,
|
||||
agent_prompt: parameters.agent_prompt || null,
|
||||
message: output.message || null,
|
||||
trigger: output.trigger || 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,
|
||||
slug: args.slug || null,
|
||||
profile_id: args.profile_id || null,
|
||||
connected_account_id: args.connected_account_id || null,
|
||||
trigger_config: args.trigger_config || null,
|
||||
name: args.name || null,
|
||||
agent_prompt: args.agent_prompt || null,
|
||||
message: toolOutput.message || null,
|
||||
trigger: toolOutput.trigger || null,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultResult;
|
||||
} catch (error) {
|
||||
console.error('Error extracting create event trigger data:', error);
|
||||
return defaultResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Zap,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Bell,
|
||||
Settings,
|
||||
FileText,
|
||||
Activity,
|
||||
Play,
|
||||
Link2,
|
||||
Bot,
|
||||
Code2
|
||||
} 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 { extractCreateEventTriggerData } from './_utils';
|
||||
|
||||
export function CreateEventTriggerToolView({
|
||||
name = 'create-event-trigger',
|
||||
assistantContent,
|
||||
toolContent,
|
||||
assistantTimestamp,
|
||||
toolTimestamp,
|
||||
isSuccess = true,
|
||||
isStreaming = false,
|
||||
}: ToolViewProps) {
|
||||
|
||||
const {
|
||||
slug,
|
||||
profile_id,
|
||||
connected_account_id,
|
||||
trigger_config,
|
||||
name: triggerName,
|
||||
agent_prompt,
|
||||
message,
|
||||
trigger,
|
||||
actualIsSuccess,
|
||||
actualToolTimestamp,
|
||||
actualAssistantTimestamp
|
||||
} = extractCreateEventTriggerData(
|
||||
assistantContent,
|
||||
toolContent,
|
||||
isSuccess,
|
||||
toolTimestamp,
|
||||
assistantTimestamp
|
||||
);
|
||||
|
||||
const toolTitle = getToolTitle(name);
|
||||
|
||||
const formatSlugName = (slug: string): string => {
|
||||
return slug
|
||||
.replace(/_/g, ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0) + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const formatConfigKey = (key: string): string => {
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/_/g, ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
.trim();
|
||||
};
|
||||
|
||||
const formatConfigValue = (value: any): string => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
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">
|
||||
<Zap 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 ? 'Trigger created' : 'Creation failed'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0 h-full flex-1 overflow-hidden relative">
|
||||
{isStreaming ? (
|
||||
<LoadingState
|
||||
icon={Zap}
|
||||
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="Creating event trigger"
|
||||
filePath={triggerName ? `"${triggerName}"` : undefined}
|
||||
showProgress={true}
|
||||
/>
|
||||
) : actualIsSuccess && trigger ? (
|
||||
<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">
|
||||
<Bell 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">
|
||||
{triggerName || 'Event Trigger'}
|
||||
</h3>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{slug ? formatSlugName(slug) : 'Custom Event'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={trigger.is_active ? "default" : "secondary"}
|
||||
className={cn(
|
||||
"text-xs",
|
||||
trigger.is_active
|
||||
? "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"
|
||||
)}
|
||||
>
|
||||
{trigger.is_active ? (
|
||||
<>
|
||||
<Activity className="h-3 w-3 mr-1" />
|
||||
Active
|
||||
</>
|
||||
) : 'Inactive'}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Link2 className="h-3 w-3 mr-1" />
|
||||
{trigger.provider || 'Provider'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
|
||||
<Zap className="w-3 h-3" />
|
||||
<span className="text-xs">Trigger Type</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 pl-5">
|
||||
{trigger.slug || slug || 'Event Trigger'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-zinc-500 dark:text-zinc-400">
|
||||
<Play className="w-3 h-3" />
|
||||
<span className="text-xs">Status</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 pl-5">
|
||||
{trigger.is_active ? 'Running' : 'Stopped'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{trigger_config && Object.keys(trigger_config).length > 0 && (
|
||||
<div className="border rounded-xl p-4 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="w-4 h-4 text-zinc-600 dark:text-zinc-400" />
|
||||
<h4 className="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
Trigger Configuration
|
||||
</h4>
|
||||
</div>
|
||||
<div className="bg-zinc-50 dark:bg-zinc-800/50 rounded-lg p-3 space-y-2">
|
||||
{Object.entries(trigger_config).map(([key, value]) => (
|
||||
<div key={key} className="flex items-start gap-2">
|
||||
<span className="text-sm font-medium text-zinc-700 dark:text-zinc-300 min-w-[120px]">
|
||||
{formatConfigKey(key)}:
|
||||
</span>
|
||||
<span className="text-sm text-zinc-600 dark:text-zinc-400 font-mono break-all">
|
||||
{formatConfigValue(value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{agent_prompt && (
|
||||
<div className="border rounded-xl p-4 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="w-4 h-4 text-zinc-600 dark:text-zinc-400" />
|
||||
<h4 className="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
Agent Execution Prompt
|
||||
</h4>
|
||||
</div>
|
||||
<div className="bg-zinc-50 dark:bg-zinc-800/50 rounded-lg p-3">
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap">
|
||||
{agent_prompt}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(connected_account_id || profile_id) && (
|
||||
<div className="border rounded-xl p-4 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Code2 className="w-4 h-4 text-zinc-600 dark:text-zinc-400" />
|
||||
<h4 className="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
Connection Details
|
||||
</h4>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
{connected_account_id && (
|
||||
<div className="space-y-1">
|
||||
<span className="text-zinc-500 dark:text-zinc-400">Account ID</span>
|
||||
<p className="font-mono text-zinc-700 dark:text-zinc-300">
|
||||
{connected_account_id}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{profile_id && (
|
||||
<div className="space-y-1">
|
||||
<span className="text-zinc-500 dark:text-zinc-400">Profile ID</span>
|
||||
<p className="font-mono text-zinc-700 dark:text-zinc-300">
|
||||
{profile_id}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 dark:from-purple-900/20 dark:to-blue-900/20 rounded-lg p-4 border border-purple-200 dark:border-purple-800">
|
||||
<div className="flex items-center gap-2 text-sm text-purple-700 dark:text-purple-400">
|
||||
<div className="w-2 h-2 bg-purple-500 rounded-full animate-pulse" />
|
||||
This trigger is now active and will automatically execute the agent when the configured event occurs.
|
||||
</div>
|
||||
</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 create event trigger. Please check your configuration and try again.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
import { parseToolResult } from '../tool-result-parser';
|
||||
|
||||
export interface TriggerConfig {
|
||||
properties: Record<string, any>;
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface TriggerPayload {
|
||||
properties: Record<string, any>;
|
||||
title?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface ToolkitInfo {
|
||||
slug: string;
|
||||
name: string;
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
export interface EventTrigger {
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
instructions?: string;
|
||||
toolkit?: ToolkitInfo;
|
||||
config?: TriggerConfig;
|
||||
payload?: TriggerPayload;
|
||||
}
|
||||
|
||||
export interface ListAppEventTriggersData {
|
||||
toolkit_slug: string | null;
|
||||
message: string | null;
|
||||
items: EventTrigger[];
|
||||
toolkit: ToolkitInfo | null;
|
||||
total: 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 extractListAppEventTriggersData(
|
||||
assistantContent?: string,
|
||||
toolContent?: any,
|
||||
isSuccess?: boolean,
|
||||
toolTimestamp?: string,
|
||||
assistantTimestamp?: string
|
||||
): ListAppEventTriggersData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} {
|
||||
const defaultResult: ListAppEventTriggersData & {
|
||||
actualIsSuccess: boolean;
|
||||
actualToolTimestamp: string | undefined;
|
||||
actualAssistantTimestamp: string | undefined;
|
||||
} = {
|
||||
toolkit_slug: null,
|
||||
message: null,
|
||||
items: [],
|
||||
toolkit: null,
|
||||
total: 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,
|
||||
toolkit_slug: args.toolkit_slug || null,
|
||||
message: output.message || null,
|
||||
items: output.items || [],
|
||||
toolkit: output.toolkit || null,
|
||||
total: output.total || 0,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content && typeof content === 'object' && content.tool === 'list-app-event-triggers') {
|
||||
const parameters = content.parameters;
|
||||
const output = content.output;
|
||||
|
||||
if (parameters && output) {
|
||||
return {
|
||||
...defaultResult,
|
||||
toolkit_slug: parameters.toolkit_slug || null,
|
||||
message: output.message || null,
|
||||
items: output.items || [],
|
||||
toolkit: output.toolkit || null,
|
||||
total: output.total || 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,
|
||||
toolkit_slug: args.toolkit_slug || null,
|
||||
message: toolOutput.message || null,
|
||||
items: toolOutput.items || [],
|
||||
toolkit: toolOutput.toolkit || null,
|
||||
total: toolOutput.total || 0,
|
||||
actualIsSuccess: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultResult;
|
||||
} catch (error) {
|
||||
console.error('Error extracting list app event triggers data:', error);
|
||||
return defaultResult;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Bell,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Zap,
|
||||
Clock,
|
||||
Settings,
|
||||
FileText,
|
||||
Package,
|
||||
Info,
|
||||
Code2,
|
||||
ChevronDown,
|
||||
ChevronUp
|
||||
} 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 { extractListAppEventTriggersData, EventTrigger } from './_utils';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
|
||||
export function ListAppEventTriggersToolView({
|
||||
name = 'list-app-event-triggers',
|
||||
assistantContent,
|
||||
toolContent,
|
||||
assistantTimestamp,
|
||||
toolTimestamp,
|
||||
isSuccess = true,
|
||||
isStreaming = false,
|
||||
}: ToolViewProps) {
|
||||
|
||||
const {
|
||||
toolkit_slug,
|
||||
message,
|
||||
items,
|
||||
toolkit,
|
||||
total,
|
||||
actualIsSuccess,
|
||||
actualToolTimestamp,
|
||||
actualAssistantTimestamp
|
||||
} = extractListAppEventTriggersData(
|
||||
assistantContent,
|
||||
toolContent,
|
||||
isSuccess,
|
||||
toolTimestamp,
|
||||
assistantTimestamp
|
||||
);
|
||||
|
||||
const [expandedTriggers, setExpandedTriggers] = React.useState<Set<string>>(new Set());
|
||||
|
||||
const toolTitle = getToolTitle(name);
|
||||
|
||||
const toggleTrigger = (slug: string) => {
|
||||
setExpandedTriggers(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(slug)) {
|
||||
newSet.delete(slug);
|
||||
} else {
|
||||
newSet.add(slug);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const getTriggerTypeIcon = (type: string) => {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'poll':
|
||||
return <Clock className="w-3 h-3" />;
|
||||
case 'webhook':
|
||||
return <Zap className="w-3 h-3" />;
|
||||
default:
|
||||
return <Bell className="w-3 h-3" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getTriggerTypeBadgeVariant = (type: string) => {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'poll':
|
||||
return "secondary";
|
||||
case 'webhook':
|
||||
return "default";
|
||||
default:
|
||||
return "outline";
|
||||
}
|
||||
};
|
||||
|
||||
const formatPropertyName = (key: string): string => {
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/_/g, ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
.trim();
|
||||
};
|
||||
|
||||
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-orange-500/20 to-orange-600/10 border border-orange-500/20">
|
||||
<Bell className="w-5 h-5 text-orange-500 dark:text-orange-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 ? 'Triggers loaded' : 'Failed to load'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0 h-full flex-1 overflow-hidden relative">
|
||||
{isStreaming ? (
|
||||
<LoadingState
|
||||
icon={Bell}
|
||||
iconColor="text-orange-500 dark:text-orange-400"
|
||||
bgColor="bg-gradient-to-b from-orange-100 to-orange-50 shadow-inner dark:from-orange-800/40 dark:to-orange-900/60 dark:shadow-orange-950/20"
|
||||
title="Loading event triggers"
|
||||
showProgress={true}
|
||||
/>
|
||||
) : actualIsSuccess && toolkit ? (
|
||||
<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">
|
||||
{toolkit.logo && (
|
||||
<img
|
||||
src={toolkit.logo}
|
||||
alt={toolkit.name}
|
||||
className="w-12 h-12 rounded-lg object-cover border border-zinc-200 dark:border-zinc-700"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-zinc-900 dark:text-zinc-100">
|
||||
{toolkit.name} Event Triggers
|
||||
</h3>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{message || `${total} trigger${total !== 1 ? 's' : ''} available`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Package className="w-3 h-3 mr-1" />
|
||||
{toolkit_slug}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{items.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{items.map((trigger, index) => (
|
||||
<Collapsible
|
||||
key={trigger.slug || index}
|
||||
open={expandedTriggers.has(trigger.slug)}
|
||||
>
|
||||
<div className="border rounded-xl overflow-hidden">
|
||||
<CollapsibleTrigger
|
||||
onClick={() => toggleTrigger(trigger.slug)}
|
||||
className="w-full p-4 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3 text-left">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-orange-100 to-orange-50 dark:from-orange-900/40 dark:to-orange-800/20 border border-orange-200 dark:border-orange-800 flex items-center justify-center">
|
||||
<Zap className="w-5 h-5 text-orange-600 dark:text-orange-400" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{trigger.name}
|
||||
</h4>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{trigger.description}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<Badge
|
||||
variant={getTriggerTypeBadgeVariant(trigger.type)}
|
||||
className="text-xs"
|
||||
>
|
||||
{getTriggerTypeIcon(trigger.type)}
|
||||
<span className="ml-1">{trigger.type}</span>
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{trigger.slug}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{expandedTriggers.has(trigger.slug) ? (
|
||||
<ChevronUp className="w-4 h-4 text-zinc-400" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4 text-zinc-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="px-4 pb-4 space-y-4 border-t">
|
||||
{trigger.instructions && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||
<Info className="w-4 h-4" />
|
||||
Instructions
|
||||
</div>
|
||||
<div className="text-sm text-zinc-600 dark:text-zinc-400 whitespace-pre-wrap bg-zinc-50 dark:bg-zinc-800/50 rounded-lg p-3">
|
||||
{trigger.instructions.trim()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{trigger.config && trigger.config.properties && Object.keys(trigger.config.properties).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||
<Settings className="w-4 h-4" />
|
||||
Configuration Parameters
|
||||
</div>
|
||||
<div className="bg-zinc-50 dark:bg-zinc-800/50 rounded-lg p-3 space-y-2">
|
||||
{Object.entries(trigger.config.properties).map(([key, prop]: [string, any]) => (
|
||||
<div key={key} className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-zinc-700 dark:text-zinc-300">
|
||||
{formatPropertyName(key)}
|
||||
</span>
|
||||
{prop.default !== undefined && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Default: {String(prop.default)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{prop.description && (
|
||||
<p className="text-xs text-zinc-600 dark:text-zinc-400 pl-2">
|
||||
{prop.description}
|
||||
</p>
|
||||
)}
|
||||
{prop.examples && prop.examples.length > 0 && (
|
||||
<div className="text-xs text-zinc-500 dark:text-zinc-500 pl-2">
|
||||
Examples: {prop.examples.join(', ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{trigger.payload && trigger.payload.properties && Object.keys(trigger.payload.properties).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||
<Code2 className="w-4 h-4" />
|
||||
Payload Structure
|
||||
</div>
|
||||
<div className="bg-zinc-50 dark:bg-zinc-800/50 rounded-lg p-3 space-y-2">
|
||||
{Object.entries(trigger.payload.properties).map(([key, prop]: [string, any]) => (
|
||||
<div key={key} className="flex items-center justify-between">
|
||||
<span className="text-xs font-mono text-zinc-700 dark:text-zinc-300">
|
||||
{key}
|
||||
</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{prop.type || 'any'}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</div>
|
||||
</Collapsible>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="border rounded-xl p-6 text-center">
|
||||
<Bell 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 event triggers found for {toolkit?.name || 'this toolkit'}
|
||||
</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 load event triggers. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -86,6 +86,8 @@ export function getToolTitle(toolName: string): string {
|
|||
'discover-user-mcp-servers': 'Discovering tools',
|
||||
'configure-agent-integration': 'Configure Agent Integration',
|
||||
'list-available-integrations': 'List Available Integrations',
|
||||
'list-app-event-triggers': 'List Event Triggers',
|
||||
'create-event-trigger': 'Create Event Trigger',
|
||||
'create-agent-scheduled-trigger': 'Create Scheduled Trigger',
|
||||
'list-agent-scheduled-triggers': 'List Scheduled Triggers',
|
||||
'delete-agent-scheduled-trigger': 'Delete Scheduled Trigger',
|
||||
|
@ -1312,6 +1314,10 @@ export function getToolComponent(toolName: string): string {
|
|||
return 'UpdateAgentToolView';
|
||||
case 'discover-user-mcp-servers':
|
||||
return 'DiscoverUserMcpServersToolView';
|
||||
case 'list-app-event-triggers':
|
||||
return 'ListAppEventTriggersToolView';
|
||||
case 'create-event-trigger':
|
||||
return 'CreateEventTriggerToolView';
|
||||
|
||||
//Deploy
|
||||
case 'deploy':
|
||||
|
|
|
@ -52,6 +52,8 @@ import { SearchMcpServersForAgentToolView } from '../search-mcp-servers-for-agen
|
|||
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 { ListAppEventTriggersToolView } from '../list-app-event-triggers/list-app-event-triggers';
|
||||
import { CreateEventTriggerToolView } from '../create-event-trigger/create-event-trigger';
|
||||
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';
|
||||
|
@ -196,6 +198,8 @@ const defaultRegistry: ToolViewRegistryType = {
|
|||
'create-credential-profile-for-agent': CreateCredentialProfileForAgentToolView,
|
||||
'discover-mcp-tools-for-agent': DiscoverMcpToolsForAgentToolView,
|
||||
'discover-user-mcp-servers': DiscoverUserMcpServersToolView,
|
||||
'list-app-event-triggers': ListAppEventTriggersToolView,
|
||||
'create-event-trigger': CreateEventTriggerToolView,
|
||||
'configure-agent-integration': ConfigureAgentIntegrationToolView,
|
||||
'create-agent-scheduled-trigger': CreateAgentScheduledTriggerToolView,
|
||||
};
|
||||
|
|
|
@ -362,6 +362,11 @@ const TOOL_DISPLAY_NAMES = new Map([
|
|||
['configure-mcp-server', 'Configuring MCP Server'],
|
||||
['get-popular-mcp-servers', 'Getting Popular MCP Servers'],
|
||||
['test-mcp-server-connection', 'Testing MCP Server Connection'],
|
||||
['list_app_event_triggers', 'Finding event triggers'],
|
||||
['list-app-event-triggers', 'Finding event triggers'],
|
||||
['create-event-trigger', 'Creating event trigger'],
|
||||
['create_event_trigger', 'Creating event trigger'],
|
||||
|
||||
|
||||
['get-project-structure', 'Getting Project Structure'],
|
||||
['build-project', 'Building Project'],
|
||||
|
|
Loading…
Reference in New Issue