'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { ArrowLeft, Settings, Loader2, Save } from 'lucide-react'; import { toast } from 'sonner'; interface SettingEntry { value: any; description: string; } export default function WorkflowSettingsPage() { const [settings, setSettings] = useState>({}); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [modified, setModified] = useState>({}); useEffect(() => { loadSettings(); }, []); const loadSettings = async () => { setIsLoading(true); try { const res = await fetch('/api/workflow/settings'); if (res.ok) { setSettings(await res.json()); setModified({}); } } catch { toast.error('Failed to load settings'); } finally { setIsLoading(false); } }; const getValue = (key: string): any => { if (key in modified) return modified[key]; return settings[key]?.value; }; const setValue = (key: string, value: any) => { setModified({ ...modified, [key]: value }); }; const handleSave = async () => { if (Object.keys(modified).length === 0) return; setIsSaving(true); try { const res = await fetch('/api/workflow/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modified), }); if (res.ok) { toast.success('Settings saved'); setSettings(await res.json()); setModified({}); } } catch { toast.error('Failed to save settings'); } finally { setIsSaving(false); } }; if (isLoading) { return (
); } return (

Workflow Settings

Configure AI providers, thresholds, and behavior

{Object.keys(modified).length > 0 && ( )}
{/* Engine Control */} Engine Control Master enable/disable for the workflow engine

When enabled, new tickets are automatically classified

setValue('workflow_engine_enabled', v)} />
{/* AI Provider Config */} AI Provider Configuration Configure API keys and models for AI-assisted classification

OpenAI

setValue('openai_api_key', e.target.value)} placeholder="sk-..." />
setValue('openai_model', e.target.value)} />

Anthropic

setValue('anthropic_api_key', e.target.value)} placeholder="sk-ant-..." />
setValue('anthropic_model', e.target.value)} />
{/* AI Feature Toggles */} AI Feature Toggles Control when AI is used during triage {[ { key: 'ai_for_title_cleanup', label: 'Title Cleanup', desc: 'Use AI to clean up messy ticket titles (email subjects, long titles)' }, { key: 'ai_for_description_rewrite', label: 'Description Rewrite', desc: 'Use AI to restructure unstructured ticket descriptions' }, { key: 'ai_for_ambiguous_classification', label: 'Ambiguous Classification', desc: 'Use AI when robotic classifier has no confident match' }, { key: 'ai_for_troubleshooting', label: 'Troubleshooting Steps', desc: 'Generate AI troubleshooting steps for incident tickets' }, ].map(({ key, label, desc }) => (

{desc}

setValue(key, v)} />
))}
{/* Processing Config */} Processing Configuration Thresholds, delays, and retry settings

Minimum confidence to skip AI classification

Delay before writing back to Autotask to avoid WF rule conflicts

setValue('autotask_update_delay_ms', parseInt(e.target.value))} />

Max retry attempts when AI validation fails

setValue('max_ai_retries', parseInt(e.target.value))} />

Days to retain execution logs

setValue('log_retention_days', parseInt(e.target.value))} />
); }