From d5caa38e87df0eeefd1ed5c069af2627ee1fb958 Mon Sep 17 00:00:00 2001 From: Krishav Raj Singh Date: Wed, 24 Sep 2025 02:32:49 +0530 Subject: [PATCH] update event triggers --- .../triggers/agent-triggers-configuration.tsx | 21 +- .../triggers/event-based-trigger-dialog.tsx | 215 ++++++++++---- .../agents/triggers/trigger-config-dialog.tsx | 276 ------------------ .../simplified-trigger-detail-panel.tsx | 20 +- .../triggers/trigger-creation-dialog.tsx | 74 +++-- 5 files changed, 242 insertions(+), 364 deletions(-) delete mode 100644 frontend/src/components/agents/triggers/trigger-config-dialog.tsx diff --git a/frontend/src/components/agents/triggers/agent-triggers-configuration.tsx b/frontend/src/components/agents/triggers/agent-triggers-configuration.tsx index c56ce5a1..1684df80 100644 --- a/frontend/src/components/agents/triggers/agent-triggers-configuration.tsx +++ b/frontend/src/components/agents/triggers/agent-triggers-configuration.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react'; import { Zap } from 'lucide-react'; import { Dialog } from '@/components/ui/dialog'; import { ConfiguredTriggersList } from './configured-triggers-list'; -import { TriggerConfigDialog } from './trigger-config-dialog'; +import { TriggerCreationDialog } from '../../triggers/trigger-creation-dialog'; import { TriggerConfiguration, TriggerProvider } from './types'; import { useAgentTriggers, @@ -155,16 +155,15 @@ export const AgentTriggersConfiguration: React.FC setConfiguringProvider(null)}> - setConfiguringProvider(null)} - isLoading={createTriggerMutation.isPending || updateTriggerMutation.isPending} - agentId={agentId} - /> - + setConfiguringProvider(null)} + type={configuringProvider.provider_id === 'schedule' ? 'schedule' : 'event'} + isEditMode={!!editingTrigger} + existingTrigger={editingTrigger} + onTriggerCreated={handleSaveTrigger} + onTriggerUpdated={handleSaveTrigger} + /> )} ); diff --git a/frontend/src/components/agents/triggers/event-based-trigger-dialog.tsx b/frontend/src/components/agents/triggers/event-based-trigger-dialog.tsx index e917ad2b..4e1af9d3 100644 --- a/frontend/src/components/agents/triggers/event-based-trigger-dialog.tsx +++ b/frontend/src/components/agents/triggers/event-based-trigger-dialog.tsx @@ -13,6 +13,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Loader2, Search, ArrowLeft, Info, Zap, ChevronRight, Plus, Sparkles, CheckCircle2, Link2 } from 'lucide-react'; import { useComposioAppsWithTriggers, useComposioAppTriggers, useCreateComposioEventTrigger, ComposioTriggerType } from '@/hooks/react-query/composio/use-composio-triggers'; +import { useUpdateTrigger } from '@/hooks/react-query/triggers'; import { useComposioProfiles } from '@/hooks/react-query/composio/use-composio-profiles'; import { useComposioToolkitDetails } from '@/hooks/react-query/composio/use-composio'; import { toast } from 'sonner'; @@ -26,6 +27,9 @@ interface EventBasedTriggerDialogProps { onOpenChange: (open: boolean) => void; agentId: string; onTriggerCreated?: (triggerId: string) => void; + isEditMode?: boolean; + existingTrigger?: any; // TriggerConfiguration for edit mode + onTriggerUpdated?: (triggerId: string) => void; } type JSONSchema = { @@ -296,8 +300,16 @@ const DynamicConfigForm: React.FC<{ ); }; -export const EventBasedTriggerDialog: React.FC = ({ open, onOpenChange, agentId, onTriggerCreated }) => { - const [step, setStep] = useState<'apps' | 'triggers' | 'config'>('apps'); +export const EventBasedTriggerDialog: React.FC = ({ + open, + onOpenChange, + agentId, + onTriggerCreated, + isEditMode = false, + existingTrigger, + onTriggerUpdated +}) => { + const [step, setStep] = useState<'apps' | 'triggers' | 'config'>(isEditMode ? 'config' : 'apps'); const [search, setSearch] = useState(''); const [selectedApp, setSelectedApp] = useState<{ slug: string; name: string; logo?: string } | null>(null); const [selectedTrigger, setSelectedTrigger] = useState(null); @@ -333,10 +345,11 @@ export const EventBasedTriggerDialog: React.FC = ( }, [allProfiles]); const createTrigger = useCreateComposioEventTrigger(); + const updateTrigger = useUpdateTrigger(); useEffect(() => { if (!open) { - setStep('apps'); + setStep(isEditMode ? 'config' : 'apps'); setSelectedApp(null); setSelectedTrigger(null); setConfig({}); @@ -348,7 +361,7 @@ export const EventBasedTriggerDialog: React.FC = ( setWorkflowInput({}); setShowComposioConnector(false); } - }, [open]); + }, [open, isEditMode]); useEffect(() => { if (selectedTrigger) { @@ -369,6 +382,65 @@ export const EventBasedTriggerDialog: React.FC = ( } }, [profiles, profileId]); + // Initialize form for edit mode + useEffect(() => { + if (isEditMode && existingTrigger && open) { + console.log('Edit mode - existingTrigger:', existingTrigger); + const triggerConfig = existingTrigger.config || {}; + console.log('Edit mode - triggerConfig:', triggerConfig); + + // Set basic info + setName(existingTrigger.name || ''); + setPrompt(triggerConfig.agent_prompt || ''); + setProfileId(triggerConfig.profile_id || ''); + setExecutionType(triggerConfig.execution_type || 'agent'); + setSelectedWorkflowId(triggerConfig.workflow_id || ''); + setWorkflowInput(triggerConfig.workflow_input || {}); + + // Set trigger config (excluding execution-specific fields) + const { agent_prompt, workflow_id, workflow_input, execution_type, profile_id, ...triggerSpecificConfig } = triggerConfig; + setConfig(triggerSpecificConfig); + + // For composio triggers, we need to reconstruct the app and trigger selection + if (triggerConfig.provider_id === 'composio' && triggerConfig.trigger_slug) { + console.log('Edit mode - setting up composio trigger for:', triggerConfig.qualified_name, triggerConfig.trigger_slug); + + // Extract toolkit slug from qualified_name (e.g., "composio.googledocs" -> "googledocs") + const toolkitSlug = triggerConfig.qualified_name?.replace('composio.', '') || ''; + + if (toolkitSlug) { + // Create app object to trigger the API call + const app = { + slug: toolkitSlug, + name: toolkitSlug, + logo: undefined + }; + setSelectedApp(app); + } + } + } + }, [isEditMode, existingTrigger, open]); + + // Find the matching trigger for edit mode once triggers are fetched + useEffect(() => { + if (isEditMode && existingTrigger && selectedApp && triggersData?.items) { + const triggerConfig = existingTrigger.config || {}; + console.log('Edit mode - looking for trigger with slug:', triggerConfig.trigger_slug); + console.log('Edit mode - available triggers:', triggersData.items.map(t => t.slug)); + + if (triggerConfig.trigger_slug) { + // Find the matching trigger from the fetched data + const matchingTrigger = triggersData.items.find(t => t.slug === triggerConfig.trigger_slug); + if (matchingTrigger) { + console.log('Edit mode - found matching trigger:', matchingTrigger); + setSelectedTrigger(matchingTrigger); + } else { + console.log('Edit mode - no matching trigger found for slug:', triggerConfig.trigger_slug); + } + } + } + }, [isEditMode, existingTrigger, selectedApp, triggersData]); + const selectedWorkflow = useMemo(() => { return (workflows || []).find((w: any) => w.id === selectedWorkflowId); }, [workflows, selectedWorkflowId]); @@ -409,30 +481,61 @@ export const EventBasedTriggerDialog: React.FC = ( const handleCreate = async () => { if (!agentId || !profileId || !selectedTrigger) return; try { - const selectedProfile = profiles?.find(p => p.profile_id === profileId); - const base: any = { - agent_id: agentId, - profile_id: profileId, - slug: selectedTrigger.slug, - trigger_config: config, - name: name || `${selectedTrigger.toolkit.name} → ${executionType === 'agent' ? 'Agent' : 'Workflow'}`, - connected_account_id: selectedProfile?.connected_account_id, - toolkit_slug: selectedApp?.slug, - }; - const payload = executionType === 'agent' - ? { ...base, route: 'agent' as const, agent_prompt: (prompt || 'Read this') } - : { ...base, route: 'workflow' as const, workflow_id: selectedWorkflowId, workflow_input: workflowInput }; - const result = await createTrigger.mutateAsync(payload); - toast.success('Task created'); + if (isEditMode && existingTrigger) { + // Update existing trigger using the general update API + const updatedConfig = { + ...config, + profile_id: profileId, + trigger_slug: selectedTrigger.slug, + qualified_name: `composio.${selectedApp?.slug}`, + provider_id: 'composio', + execution_type: executionType, + ...(executionType === 'agent' ? { agent_prompt: prompt || 'Read this' } : { + workflow_id: selectedWorkflowId, + workflow_input: workflowInput + }) + }; - if (onTriggerCreated && result?.trigger_id) { - onTriggerCreated(result.trigger_id); + await updateTrigger.mutateAsync({ + triggerId: existingTrigger.trigger_id, + name: name || `${selectedTrigger.toolkit.name} → ${executionType === 'agent' ? 'Agent' : 'Workflow'}`, + description: `Event trigger for ${selectedTrigger.toolkit.name}`, + config: updatedConfig, + is_active: true, + }); + toast.success('Task updated'); + + if (onTriggerUpdated && existingTrigger.trigger_id) { + onTriggerUpdated(existingTrigger.trigger_id); + } + } else { + // Create new trigger using Composio-specific API + const selectedProfile = profiles?.find(p => p.profile_id === profileId); + const base: any = { + agent_id: agentId, + profile_id: profileId, + slug: selectedTrigger.slug, + trigger_config: config, + name: name || `${selectedTrigger.toolkit.name} → ${executionType === 'agent' ? 'Agent' : 'Workflow'}`, + connected_account_id: selectedProfile?.connected_account_id, + toolkit_slug: selectedApp?.slug, + }; + const payload = executionType === 'agent' + ? { ...base, route: 'agent' as const, agent_prompt: (prompt || 'Read this') } + : { ...base, route: 'workflow' as const, workflow_id: selectedWorkflowId, workflow_input: workflowInput }; + + const result = await createTrigger.mutateAsync(payload); + toast.success('Task created'); + + if (onTriggerCreated && result?.trigger_id) { + onTriggerCreated(result.trigger_id); + } } onOpenChange(false); } catch (e: any) { // Handle nested error structure from API - let errorMessage = 'Failed to create trigger'; + let errorMessage = isEditMode ? 'Failed to update trigger' : 'Failed to create trigger'; console.error('Error creating trigger:', e); console.error('Error details:', e?.details); console.error('Error keys:', Object.keys(e || {})); @@ -486,7 +589,9 @@ export const EventBasedTriggerDialog: React.FC = ( )} - Create Event Trigger + + {isEditMode ? 'Edit Event Trigger' : 'Create Event Trigger'} + @@ -612,12 +717,22 @@ export const EventBasedTriggerDialog: React.FC = ( )} - {step === 'config' && selectedTrigger && ( + {step === 'config' && (
-
+ {/* Loading state for edit mode while waiting for trigger data */} + {isEditMode && !selectedTrigger ? ( +
+
+ +

Loading trigger configuration...

+
+
+ ) : selectedTrigger ? ( + <> +
{selectedTrigger.instructions && ( @@ -800,27 +915,29 @@ export const EventBasedTriggerDialog: React.FC = (
- {/* Fixed Footer */} - {(!loadingProfiles && (profiles || []).filter(p => p.is_connected).length > 0) && ( -
-
- -
-
- )} + {/* Fixed Footer */} + {(!loadingProfiles && (profiles || []).filter(p => p.is_connected).length > 0) && ( +
+
+ +
+
+ )} + + ) : null}
)}
diff --git a/frontend/src/components/agents/triggers/trigger-config-dialog.tsx b/frontend/src/components/agents/triggers/trigger-config-dialog.tsx deleted file mode 100644 index 6e0133e5..00000000 --- a/frontend/src/components/agents/triggers/trigger-config-dialog.tsx +++ /dev/null @@ -1,276 +0,0 @@ -"use client"; - -import React, { useState, useEffect } from 'react'; -import { - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { Switch } from '@/components/ui/switch'; -import { - Activity, - Copy, - ExternalLink, - Loader2 -} from 'lucide-react'; -import { TriggerProvider, TriggerConfiguration, ScheduleTriggerConfig, EventTriggerConfig } from './types'; -import { SimplifiedScheduleConfig } from './providers/simplified-schedule-config'; -import { EventTriggerConfigForm } from './providers/event-config'; -import { getDialogIcon } from './utils'; - - -interface TriggerConfigDialogProps { - provider: TriggerProvider; - existingConfig?: TriggerConfiguration; - onSave: (config: any) => void; - onCancel: () => void; - isLoading?: boolean; - agentId: string; - selectedAgent?: string; - onAgentSelect?: (agentId: string) => void; - open?: boolean; - onOpenChange?: (open: boolean) => void; -} - -export const TriggerConfigDialog: React.FC = ({ - provider, - existingConfig, - onSave, - onCancel, - isLoading = false, - agentId, - selectedAgent, - onAgentSelect, - open = true, - onOpenChange -}) => { - const [name, setName] = useState(existingConfig?.name || ''); - const [description, setDescription] = useState(existingConfig?.description || ''); - const [isActive, setIsActive] = useState(existingConfig?.is_active ?? true); - const [config, setConfig] = useState(existingConfig?.config || {}); - const [errors, setErrors] = useState>({}); - - useEffect(() => { - if (!name && !existingConfig) { - setName(`${provider.name} Trigger`); - } - }, [provider, existingConfig, name]); - - const validateForm = () => { - const newErrors: Record = {}; - if (!name.trim()) { - newErrors.name = 'Name is required'; - } - if (provider.provider_id === 'telegram') { - if (!config.bot_token) { - newErrors.bot_token = 'Bot token is required'; - } - } else if (provider.provider_id === 'slack') { - if (!config.signing_secret) { - newErrors.signing_secret = 'Signing secret is required'; - } - } else if (provider.provider_id === 'schedule') { - if (!config.cron_expression) { - newErrors.cron_expression = 'Cron expression is required'; - } - if (config.execution_type === 'workflow') { - if (!config.workflow_id) { - newErrors.workflow_id = 'Workflow selection is required'; - } - } else { - if (!config.agent_prompt) { - newErrors.agent_prompt = 'Agent prompt is required'; - } - } - } else if (provider.trigger_type === 'webhook' || provider.provider_id === 'composio') { - // Validate event-based triggers - if (config.execution_type === 'workflow') { - if (!config.workflow_id) { - newErrors.workflow_id = 'Workflow selection is required'; - } - } else { - if (!config.agent_prompt) { - newErrors.agent_prompt = 'Agent prompt is required'; - } - } - } - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSave = () => { - if (validateForm()) { - onSave({ - name: name.trim(), - description: description.trim(), - is_active: isActive, - config, - }); - } - }; - - if (provider.provider_id === 'schedule') { - return ( - - ); - } - - const renderProviderSpecificConfig = () => { - switch (provider.provider_id) { - case 'schedule': - return ( - - ); - case 'composio': - return ( - - ); - default: - if (provider.trigger_type === 'webhook') { - return ( - - ); - } - return ( -
- -

Configuration form for {provider.name} is not yet implemented.

-
- ); - } - }; - - return ( - -
- {renderProviderSpecificConfig()} - {/* {provider.webhook_enabled && existingConfig?.webhook_url && ( -
-
-

Webhook Information

-
- -
- - - -
-

- Use this URL to configure the webhook in {provider.name} -

-
-
-
- )} */} -
-
-
- - -
-
-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/triggers/simplified-trigger-detail-panel.tsx b/frontend/src/components/triggers/simplified-trigger-detail-panel.tsx index fda2ab51..9abcb0ea 100644 --- a/frontend/src/components/triggers/simplified-trigger-detail-panel.tsx +++ b/frontend/src/components/triggers/simplified-trigger-detail-panel.tsx @@ -26,7 +26,7 @@ import { import Link from 'next/link'; import { TriggerWithAgent } from '@/hooks/react-query/triggers/use-all-triggers'; import { useDeleteTrigger, useToggleTrigger, useUpdateTrigger } from '@/hooks/react-query/triggers'; -import { TriggerConfigDialog } from '@/components/agents/triggers/trigger-config-dialog'; +import { TriggerCreationDialog } from './trigger-creation-dialog'; import { useAgentWorkflows } from '@/hooks/react-query/agents/use-agent-workflows'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; @@ -334,16 +334,14 @@ export function SimplifiedTriggerDetailPanel({ trigger, onClose }: SimplifiedTri {/* Edit Dialog */} {showEditDialog && ( - - setShowEditDialog(false)} - isLoading={updateMutation.isPending} - agentId={trigger.agent_id} - /> - + )} {/* Delete Dialog */} diff --git a/frontend/src/components/triggers/trigger-creation-dialog.tsx b/frontend/src/components/triggers/trigger-creation-dialog.tsx index 1447bdfa..a02fbee6 100644 --- a/frontend/src/components/triggers/trigger-creation-dialog.tsx +++ b/frontend/src/components/triggers/trigger-creation-dialog.tsx @@ -13,7 +13,7 @@ import { ArrowRight, Clock, PlugZap } from 'lucide-react'; import { EventBasedTriggerDialog } from '@/components/agents/triggers/event-based-trigger-dialog'; import { SimplifiedScheduleConfig } from '@/components/agents/triggers/providers/simplified-schedule-config'; import { ScheduleTriggerConfig } from '@/components/agents/triggers/types'; -import { useCreateTrigger } from '@/hooks/react-query/triggers'; +import { useCreateTrigger, useUpdateTrigger } from '@/hooks/react-query/triggers'; import { toast } from 'sonner'; import { AgentSelectionDropdown } from '@/components/agents/agent-selection-dropdown'; @@ -22,13 +22,19 @@ interface TriggerCreationDialogProps { onOpenChange: (open: boolean) => void; type: 'schedule' | 'event'; onTriggerCreated?: (triggerId: string) => void; + isEditMode?: boolean; + existingTrigger?: any; // TriggerConfiguration for edit mode + onTriggerUpdated?: (triggerId: string) => void; } export function TriggerCreationDialog({ open, onOpenChange, type, - onTriggerCreated + onTriggerCreated, + isEditMode = false, + existingTrigger, + onTriggerUpdated }: TriggerCreationDialogProps) { const [selectedAgent, setSelectedAgent] = useState(''); const [step, setStep] = useState<'agent' | 'config'>('agent'); @@ -39,6 +45,19 @@ export function TriggerCreationDialog({ execution_type: 'agent' }); const createTriggerMutation = useCreateTrigger(); + const updateTriggerMutation = useUpdateTrigger(); + + // Initialize form for edit mode + React.useEffect(() => { + if (isEditMode && existingTrigger && open) { + setSelectedAgent(existingTrigger.agent_id || ''); + setName(existingTrigger.name || ''); + setDescription(existingTrigger.description || ''); + setConfig(existingTrigger.config || { cron_expression: '', execution_type: 'agent' }); + // Skip agent selection step in edit mode + setStep('config'); + } + }, [isEditMode, existingTrigger, open]); const scheduleProvider = { provider_id: 'schedule', @@ -55,23 +74,40 @@ export function TriggerCreationDialog({ } try { - const newTrigger = await createTriggerMutation.mutateAsync({ - agentId: selectedAgent, - provider_id: 'schedule', - name: data.name || 'Scheduled Trigger', - description: data.description || 'Automatically scheduled trigger', - config: data.config, - }); - toast.success('Schedule trigger created successfully'); + if (isEditMode && existingTrigger) { + // Update existing trigger + await updateTriggerMutation.mutateAsync({ + triggerId: existingTrigger.trigger_id, + name: data.name || 'Scheduled Trigger', + description: data.description || 'Automatically scheduled trigger', + config: data.config, + is_active: data.is_active, + }); + toast.success('Schedule trigger updated successfully'); - if (onTriggerCreated && newTrigger?.trigger_id) { - onTriggerCreated(newTrigger.trigger_id); + if (onTriggerUpdated && existingTrigger.trigger_id) { + onTriggerUpdated(existingTrigger.trigger_id); + } + } else { + // Create new trigger + const newTrigger = await createTriggerMutation.mutateAsync({ + agentId: selectedAgent, + provider_id: 'schedule', + name: data.name || 'Scheduled Trigger', + description: data.description || 'Automatically scheduled trigger', + config: data.config, + }); + toast.success('Schedule trigger created successfully'); + + if (onTriggerCreated && newTrigger?.trigger_id) { + onTriggerCreated(newTrigger.trigger_id); + } } handleClose(); } catch (error: any) { - toast.error(error.message || 'Failed to create schedule trigger'); - console.error('Error creating schedule trigger:', error); + toast.error(error.message || `Failed to ${isEditMode ? 'update' : 'create'} schedule trigger`); + console.error(`Error ${isEditMode ? 'updating' : 'creating'} schedule trigger:`, error); } }; @@ -97,17 +133,17 @@ export function TriggerCreationDialog({ {type === 'schedule' ? ( <> - Create Scheduled Task + {isEditMode ? 'Edit Scheduled Task' : 'Create Scheduled Task'} ) : ( <> - Create App-based Task + {isEditMode ? 'Edit App-based Task' : 'Create App-based Task'} )} - First, select which agent should handle this task + {isEditMode ? 'Update the agent for this task' : 'First, select which agent should handle this task'} @@ -153,6 +189,7 @@ export function TriggerCreationDialog({ open={open} onOpenChange={onOpenChange} onSave={handleScheduleSave} + isEditMode={isEditMode} /> ); } @@ -163,6 +200,9 @@ export function TriggerCreationDialog({ onOpenChange={handleClose} agentId={selectedAgent} onTriggerCreated={onTriggerCreated} + isEditMode={isEditMode} + existingTrigger={existingTrigger} + onTriggerUpdated={onTriggerUpdated} /> ); } \ No newline at end of file