mirror of https://github.com/kortix-ai/suna.git
258 lines
8.1 KiB
TypeScript
258 lines
8.1 KiB
TypeScript
"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 { Badge } from '@/components/ui/badge';
|
|
import {
|
|
MessageSquare,
|
|
Webhook,
|
|
Clock,
|
|
Mail,
|
|
Github,
|
|
Gamepad2,
|
|
Activity,
|
|
Eye,
|
|
EyeOff,
|
|
Copy,
|
|
ExternalLink
|
|
} from 'lucide-react';
|
|
import { TriggerProvider, TriggerConfiguration, TelegramTriggerConfig, SlackTriggerConfig, ScheduleTriggerConfig } from './types';
|
|
import { TelegramTriggerConfigForm } from './providers/telegram-config';
|
|
import { SlackTriggerConfigForm } from './providers/slack-config';
|
|
import { WebhookTriggerConfigForm } from './providers/webhook-config';
|
|
import { ScheduleTriggerConfigForm } from './providers/schedule-config';
|
|
import { getDialogIcon } from './utils';
|
|
|
|
|
|
interface TriggerConfigDialogProps {
|
|
provider: TriggerProvider;
|
|
existingConfig?: TriggerConfiguration;
|
|
onSave: (config: any) => void;
|
|
onCancel: () => void;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export const TriggerConfigDialog: React.FC<TriggerConfigDialogProps> = ({
|
|
provider,
|
|
existingConfig,
|
|
onSave,
|
|
onCancel,
|
|
isLoading = false,
|
|
}) => {
|
|
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<Record<string, string>>({});
|
|
|
|
useEffect(() => {
|
|
if (!name && !existingConfig) {
|
|
setName(`${provider.name} Trigger`);
|
|
}
|
|
}, [provider, existingConfig, name]);
|
|
|
|
const validateForm = () => {
|
|
const newErrors: Record<string, string> = {};
|
|
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.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,
|
|
});
|
|
}
|
|
};
|
|
|
|
const renderProviderSpecificConfig = () => {
|
|
switch (provider.provider_id) {
|
|
case 'telegram':
|
|
return (
|
|
<TelegramTriggerConfigForm
|
|
config={config as TelegramTriggerConfig}
|
|
onChange={setConfig}
|
|
errors={errors}
|
|
/>
|
|
);
|
|
case 'slack':
|
|
return (
|
|
<SlackTriggerConfigForm
|
|
config={config as SlackTriggerConfig}
|
|
onChange={setConfig}
|
|
errors={errors}
|
|
/>
|
|
);
|
|
case 'schedule':
|
|
return (
|
|
<ScheduleTriggerConfigForm
|
|
provider={provider}
|
|
config={config as ScheduleTriggerConfig}
|
|
onChange={setConfig}
|
|
errors={errors}
|
|
/>
|
|
);
|
|
case 'webhook':
|
|
case 'github_webhook':
|
|
case 'discord':
|
|
return (
|
|
<WebhookTriggerConfigForm
|
|
provider={provider}
|
|
config={config}
|
|
onChange={setConfig}
|
|
errors={errors}
|
|
/>
|
|
);
|
|
default:
|
|
return (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<Activity className="h-12 w-12 mx-auto mb-4" />
|
|
<p>Configuration form for {provider.name} is not yet implemented.</p>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center space-x-3">
|
|
<div className="p-2 rounded-lg bg-muted border">
|
|
{getDialogIcon(provider.trigger_type)}
|
|
</div>
|
|
<div>
|
|
<span>Configure {provider.name}</span>
|
|
</div>
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{provider.description}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="flex-1 overflow-y-auto space-y-6 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="trigger-name">Name *</Label>
|
|
<Input
|
|
id="trigger-name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="Enter a name for this trigger"
|
|
className={errors.name ? 'border-destructive' : ''}
|
|
/>
|
|
{errors.name && (
|
|
<p className="text-sm text-destructive">{errors.name}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="trigger-description">Description</Label>
|
|
<Textarea
|
|
id="trigger-description"
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
placeholder="Optional description for this trigger"
|
|
rows={2}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-2">
|
|
<Switch
|
|
id="trigger-active"
|
|
checked={isActive}
|
|
onCheckedChange={setIsActive}
|
|
/>
|
|
<Label htmlFor="trigger-active">
|
|
Enable trigger immediately
|
|
</Label>
|
|
</div>
|
|
</div>
|
|
<div className="border-t pt-6">
|
|
<h3 className="text-sm font-medium mb-4">
|
|
{provider.name} Configuration
|
|
</h3>
|
|
{renderProviderSpecificConfig()}
|
|
</div>
|
|
{provider.webhook_enabled && existingConfig?.webhook_url && (
|
|
<div className="border-t pt-6">
|
|
<h3 className="text-sm font-medium mb-4">Webhook Information</h3>
|
|
<div className="space-y-2">
|
|
<Label>Webhook URL</Label>
|
|
<div className="flex items-center space-x-2">
|
|
<Input
|
|
value={existingConfig.webhook_url}
|
|
readOnly
|
|
className="font-mono text-sm"
|
|
/>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => navigator.clipboard.writeText(existingConfig.webhook_url!)}
|
|
>
|
|
<Copy className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => window.open(existingConfig.webhook_url, '_blank')}
|
|
>
|
|
<ExternalLink className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Use this URL to configure the webhook in {provider.name}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={onCancel} disabled={isLoading}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleSave} disabled={isLoading}>
|
|
{isLoading ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2"></div>
|
|
{existingConfig ? 'Updating...' : 'Creating...'}
|
|
</>
|
|
) : (
|
|
`${existingConfig ? 'Update' : 'Create'} Trigger`
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
);
|
|
};
|