diff --git a/frontend/src/pages/AgentsPage.tsx b/frontend/src/pages/AgentsPage.tsx index bc46c9df..baf728d0 100644 --- a/frontend/src/pages/AgentsPage.tsx +++ b/frontend/src/pages/AgentsPage.tsx @@ -133,7 +133,32 @@ function formatRelativeTime(ts?: number | null): string { function formatSchedule(type?: string, value?: string): string { if (!type || type === 'manual') return 'Manual'; - if (type === 'cron') return value ? `Cron: ${value}` : 'Cron'; + if (type === 'cron' && value) { + // Try to display human-readable for common cron patterns + const parts = value.trim().split(/\s+/); + if (parts.length === 5) { + const [min, hour, , , dow] = parts; + const hourNum = parseInt(hour, 10); + const formatHour = (h: number) => { + if (h === 0) return '12:00 AM'; + if (h < 12) return `${h}:00 AM`; + if (h === 12) return '12:00 PM'; + return `${h - 12}:00 PM`; + }; + // Daily pattern: 0 H * * * + if (min === '0' && !isNaN(hourNum) && parts[2] === '*' && parts[3] === '*' && dow === '*') { + return `Daily at ${formatHour(hourNum)}`; + } + // Weekly pattern: 0 H * * days + if (min === '0' && !isNaN(hourNum) && parts[2] === '*' && parts[3] === '*' && dow !== '*') { + const DAY_NAMES: Record = { '1': 'Mon', '2': 'Tue', '3': 'Wed', '4': 'Thu', '5': 'Fri', '6': 'Sat', '7': 'Sun' }; + const dayList = dow.split(',').map(d => DAY_NAMES[d] || d).join(', '); + return `Weekly on ${dayList} at ${formatHour(hourNum)}`; + } + } + return `Cron: ${value}`; + } + if (type === 'cron') return 'Cron'; if (type === 'interval' && value) { const total = parseInt(value); if (!isNaN(total) && total > 0) { @@ -327,9 +352,20 @@ function LaunchWizard({ if (!wizard.name.trim()) { toast.error('Name is required'); return; } setLaunching(true); try { + // Map friendly schedule presets to API schedule_type/schedule_value + let apiScheduleType = wizard.scheduleType; + let apiScheduleValue = wizard.scheduleValue; + if (wizard.scheduleType === 'daily' || wizard.scheduleType === 'weekly') { + apiScheduleType = 'cron'; + // scheduleValue already holds the cron expression + } else if (wizard.scheduleType === 'hourly') { + apiScheduleType = 'interval'; + // scheduleValue already holds seconds as string + } + const config: Record = { - schedule_type: wizard.scheduleType, - schedule_value: wizard.scheduleValue || undefined, + schedule_type: apiScheduleType, + schedule_value: apiScheduleValue || undefined, tools: wizard.selectedTools, learning_enabled: !!wizard.routerPolicy, memory_extraction: wizard.memoryExtraction, @@ -491,9 +527,87 @@ function LaunchWizard({ style={{ background: 'var(--color-bg-secondary)', border: '1px solid var(--color-border)', color: 'var(--color-text)' }} > - - + + + + + {wizard.scheduleType === 'daily' && ( + + )} + {wizard.scheduleType === 'weekly' && ( +
+
+ {(['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] as const).map((day, idx) => { + const dayNum = String(idx + 1); + const cronParts = wizard.scheduleValue.match(/\*\s+\*\s+(.+)$/); + const selectedDays = cronParts ? cronParts[1].split(',') : []; + const isSelected = selectedDays.includes(dayNum); + return ( + + ); + })} +
+ +
+ )} + {wizard.scheduleType === 'hourly' && ( +
+ Every + { const secs = parseInt(wizard.scheduleValue || '0', 10); return secs > 0 ? Math.round(secs / 3600) : 1; })()} + onChange={(e) => { + const hrs = Math.min(24, Math.max(1, parseInt(e.target.value, 10) || 1)); + setWizard((w) => ({ ...w, scheduleValue: String(hrs * 3600) })); + }} + className="w-14 px-2 py-1 rounded text-xs text-center" + style={{ background: 'var(--color-bg)', border: '1px solid var(--color-border)', color: 'var(--color-text)' }} + /> + hours +
+ )} {wizard.scheduleType === 'cron' && ( )} - {wizard.scheduleType === 'interval' && ( -
-
- { - const hrs = Math.min(24, Math.max(0, parseInt(e.target.value, 10) || 0)); - const mins = Math.floor((parseInt(wizard.scheduleValue || '0', 10) % 3600) / 60); - setWizard((w) => ({ ...w, scheduleValue: String(hrs * 3600 + mins * 60) })); - }} - className="w-14 px-2 py-1 rounded text-xs text-center" - style={{ background: 'var(--color-bg)', border: '1px solid var(--color-border)', color: 'var(--color-text)' }} - /> - hrs -
-
- { - const hrs = Math.floor(parseInt(wizard.scheduleValue || '0', 10) / 3600); - const mins = Math.min(59, Math.max(0, parseInt(e.target.value, 10) || 0)); - setWizard((w) => ({ ...w, scheduleValue: String(hrs * 3600 + mins * 60) })); - }} - className="w-14 px-2 py-1 rounded text-xs text-center" - style={{ background: 'var(--color-bg)', border: '1px solid var(--color-border)', color: 'var(--color-text)' }} - /> - min -
-
- )} @@ -616,11 +698,13 @@ function LaunchWizard({
- setWizard((w) => ({ ...w, scheduleType: e.target.value, scheduleValue: e.target.value === 'manual' ? '' : w.scheduleValue }))} className="w-full px-2 py-1 rounded text-xs" style={{ background: 'var(--color-bg)', border: '1px solid var(--color-border)', color: 'var(--color-text)' }}> - - + + + +