import { useState, useEffect } from 'react'; import type { ScheduledTask, TaskExecution, Agent } from '../types'; import { t } from '../i18n'; function useAvailableClis(): string[] { const [clis, setClis] = useState(['claude']); useEffect(() => { fetch('/api/cli-available') .then((r) => r.json()) .then((data) => { if (Array.isArray(data)) setClis(data); }) .catch(() => {}); }, []); return clis; } // Cron preset definitions const SCHEDULE_PRESETS = [ { labelKey: 'tasks.presetEvery6h', cron: '0 */6 * * *' }, { labelKey: 'tasks.presetDaily9am', cron: '0 9 * * *' }, { labelKey: 'tasks.presetDailyTwice', cron: '0 9,21 * * *' }, { labelKey: 'tasks.presetWeekly', cron: '0 9 * * 1' }, { labelKey: 'tasks.presetCustom', cron: '' }, ]; function humanReadableCron(cron: string): string { for (const p of SCHEDULE_PRESETS) { if (p.cron && p.cron === cron) return t(p.labelKey); } return cron; } function StatusBadge({ status }: { status: TaskExecution['status'] }) { const cls = status === 'running' ? 'badge-yellow' : status === 'success' ? 'badge-green' : 'text-xs font-medium px-1.5 py-0.5 rounded-full bg-red-900/40 text-red-400'; return {status}; } function formatDuration(ms: number | null): string { if (ms == null) return '-'; if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(1)}s`; } function formatDatetime(ts: string | null): string { if (!ts) return '-'; try { return new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); } catch { return ts; } } // ---- Add Task Form ---- interface AddTaskFormProps { agents: Agent[]; onSave: (data: Omit) => Promise; onCancel: () => void; } function AddTaskForm({ agents, onSave, onCancel }: AddTaskFormProps) { const [name, setName] = useState(''); const [prompt, setPrompt] = useState(''); const [agent, setAgent] = useState('claude'); const [selectedPreset, setSelectedPreset] = useState(SCHEDULE_PRESETS[0].cron); const [customCron, setCustomCron] = useState(''); const [timezone, setTimezone] = useState('Asia/Taipei'); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const availableClis = useAvailableClis(); const isCustom = selectedPreset === ''; const finalCron = isCustom ? customCron : selectedPreset; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { setError('Task name is required'); return; } if (!prompt.trim()) { setError('Prompt is required'); return; } if (!finalCron.trim()) { setError('Schedule is required'); return; } setSaving(true); setError(''); try { await onSave({ name: name.trim(), prompt: prompt.trim(), agent, schedule: finalCron.trim(), timezone, enabled: true, }); } catch (err) { setError(`Save failed: ${(err as Error).message}`); } finally { setSaving(false); } }; return (

{t('tasks.add')}

setName(e.target.value)} placeholder="Daily news summary" className="input-base w-full" />