From 905b828e9bf7c54dde64f3a4cea08e4723701a40 Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:59:21 +0800 Subject: [PATCH] fix crontab task (#19) --- electron/main/ipc-handlers.ts | 17 +++-- src/pages/Cron/index.tsx | 130 ++++++++++++++++++++-------------- 2 files changed, 90 insertions(+), 57 deletions(-) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 0a90b9352..cb5f23f0f 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -150,11 +150,11 @@ function transformCronJob(job: GatewayCronJob) { // Build lastRun from state const lastRun = job.state?.lastRunAtMs ? { - time: new Date(job.state.lastRunAtMs).toISOString(), - success: job.state.lastStatus === 'ok', - error: job.state.lastError, - duration: job.state.lastDurationMs, - } + time: new Date(job.state.lastRunAtMs).toISOString(), + success: job.state.lastStatus === 'ok', + error: job.state.lastError, + duration: job.state.lastDurationMs, + } : undefined; // Build nextRun from state @@ -208,6 +208,12 @@ function registerCronHandlers(gatewayManager: GatewayManager): void { }) => { try { // Transform frontend input to Gateway cron.add format + // For Discord, the recipient must be prefixed with "channel:" or "user:" + const recipientId = input.target.channelId; + const deliveryTo = input.target.channelType === 'discord' && recipientId + ? `channel:${recipientId}` + : recipientId; + const gatewayInput = { name: input.name, schedule: { kind: 'cron', expr: input.schedule }, @@ -218,6 +224,7 @@ function registerCronHandlers(gatewayManager: GatewayManager): void { delivery: { mode: 'announce', channel: input.target.channelType, + to: deliveryTo, }, }; const result = await gatewayManager.rpc('cron.add', gatewayInput); diff --git a/src/pages/Cron/index.tsx b/src/pages/Cron/index.tsx index 44f6f4607..359b082c8 100644 --- a/src/pages/Cron/index.tsx +++ b/src/pages/Cron/index.tsx @@ -3,13 +3,13 @@ * Manage scheduled tasks */ import { useEffect, useState, useCallback } from 'react'; -import { - Plus, - Clock, - Play, - Pause, - Trash2, - Edit, +import { + Plus, + Clock, + Play, + Pause, + Trash2, + Edit, RefreshCw, X, Calendar, @@ -90,12 +90,12 @@ function parseCronSchedule(schedule: unknown): string { function parseCronExpr(cron: string): string { const preset = schedulePresets.find((p) => p.value === cron); if (preset) return preset.label; - + const parts = cron.split(' '); if (parts.length !== 5) return cron; - + const [minute, hour, dayOfMonth, , dayOfWeek] = parts; - + if (minute === '*' && hour === '*') return 'Every minute'; if (minute.startsWith('*/')) return `Every ${minute.slice(2)} minutes`; if (hour === '*' && minute === '0') return 'Every hour'; @@ -109,7 +109,7 @@ function parseCronExpr(cron: string): string { if (hour !== '*') { return `Daily at ${hour}:${minute.padStart(2, '0')}`; } - + return cron; } @@ -123,7 +123,7 @@ interface TaskDialogProps { function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { const { channels } = useChannelsStore(); const [saving, setSaving] = useState(false); - + const [name, setName] = useState(job?.name || ''); const [message, setMessage] = useState(job?.message || ''); // Extract cron expression string from CronSchedule object or use as-is if string @@ -140,10 +140,12 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { const [customSchedule, setCustomSchedule] = useState(''); const [useCustom, setUseCustom] = useState(false); const [channelId, setChannelId] = useState(job?.target.channelId || ''); + const [discordChannelId, setDiscordChannelId] = useState(''); const [enabled, setEnabled] = useState(job?.enabled ?? true); - + const selectedChannel = channels.find((c) => c.id === channelId); - + const isDiscord = selectedChannel?.type === 'discord'; + const handleSubmit = async () => { if (!name.trim()) { toast.error('Please enter a task name'); @@ -157,22 +159,32 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { toast.error('Please select a channel'); return; } - + // Validate Discord channel ID when Discord is selected + if (selectedChannel?.type === 'discord' && !discordChannelId.trim()) { + toast.error('Please enter a Discord Channel ID'); + return; + } + const finalSchedule = useCustom ? customSchedule : schedule; if (!finalSchedule.trim()) { toast.error('Please select or enter a schedule'); return; } - + setSaving(true); try { + // For Discord, use the manually entered channel ID; for others, use empty + const actualChannelId = selectedChannel!.type === 'discord' + ? discordChannelId.trim() + : ''; + await onSave({ name: name.trim(), message: message.trim(), schedule: finalSchedule, target: { channelType: selectedChannel!.type, - channelId: selectedChannel!.id, + channelId: actualChannelId, channelName: selectedChannel!.name, }, enabled, @@ -185,7 +197,7 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { setSaving(false); } }; - + return (
e.stopPropagation()}> @@ -209,7 +221,7 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { onChange={(e) => setName(e.target.value)} />
- + {/* Message */}
@@ -221,7 +233,7 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { rows={3} />
- + {/* Schedule */}
@@ -258,7 +270,7 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { {useCustom ? 'Use presets' : 'Use custom cron'}
- + {/* Target Channel */}
@@ -284,7 +296,21 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) {
)} - + + {/* Discord Channel ID - only shown when Discord is selected */} + {isDiscord && ( +
+ + setDiscordChannelId(e.target.value)} + placeholder="e.g., 1438452657525100686" + /> +

+ Right-click the Discord channel → Copy Channel ID +

+
+ )} {/* Enabled */}
@@ -295,7 +321,7 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) {
- + {/* Actions */}
-
- + {/* Gateway Warning */} {!isGatewayRunning && ( @@ -558,7 +584,7 @@ export function Cron() { )} - + {/* Statistics */}
@@ -614,7 +640,7 @@ export function Cron() {
- + {/* Error Display */} {error && ( @@ -624,7 +650,7 @@ export function Cron() { )} - + {/* Jobs List */} {jobs.length === 0 ? ( @@ -632,10 +658,10 @@ export function Cron() {

No scheduled tasks

- Create scheduled tasks to automate AI workflows. + Create scheduled tasks to automate AI workflows. Tasks can send messages, run queries, or perform actions at specified times.

-