feat(tasks): manage markdown loops from scheduled tasks
This commit is contained in:
@@ -21,14 +21,17 @@ import { useI18n } from '@/lib/i18n';
|
||||
import type { ProjectEntry } from '@/lib/api/types';
|
||||
import {
|
||||
deleteScheduledTask,
|
||||
deleteScheduledTaskLoopFile,
|
||||
fetchScheduledTasks,
|
||||
runScheduledTaskNow,
|
||||
setLoopScheduledTaskEnabled,
|
||||
upsertScheduledTask,
|
||||
type ScheduledTask,
|
||||
type ScheduledTaskStatus,
|
||||
} from '@/lib/scheduledTasksApi';
|
||||
import { ScheduledTaskEditorDialog } from './ScheduledTaskEditorDialog';
|
||||
import { canonicalizeTimezone } from '@/lib/timezones';
|
||||
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
|
||||
|
||||
const scheduleTimes = (task: ScheduledTask): string[] => {
|
||||
const raw = Array.isArray(task.schedule.times)
|
||||
@@ -314,10 +317,11 @@ export function ScheduledTasksDialog() {
|
||||
setMutatingTaskID(task.id);
|
||||
setTasks((prev) => prev.map((item) => (item.id === task.id ? { ...item, enabled } : item)));
|
||||
try {
|
||||
await upsertScheduledTask(selectedProjectID, {
|
||||
...task,
|
||||
enabled,
|
||||
});
|
||||
if (task.loopFile) {
|
||||
await setLoopScheduledTaskEnabled(selectedProjectID, task.id, enabled);
|
||||
} else {
|
||||
await upsertScheduledTask(selectedProjectID, { ...task, enabled });
|
||||
}
|
||||
await reloadTasks(selectedProjectID, { silent: true });
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('sessions.scheduledTasks.dialog.toast.updateFailed'));
|
||||
@@ -331,14 +335,20 @@ export function ScheduledTasksDialog() {
|
||||
if (!selectedProjectID) {
|
||||
return;
|
||||
}
|
||||
const confirmed = window.confirm(t('sessions.scheduledTasks.dialog.confirm.deleteTask', { taskName: task.name }));
|
||||
const confirmed = window.confirm(task.loopFile
|
||||
? t('sessions.scheduledTasks.dialog.confirm.deleteLoopFile', { taskName: task.name })
|
||||
: t('sessions.scheduledTasks.dialog.confirm.deleteTask', { taskName: task.name }));
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMutatingTaskID(task.id);
|
||||
try {
|
||||
await deleteScheduledTask(selectedProjectID, task.id);
|
||||
if (task.loopFile) {
|
||||
await deleteScheduledTaskLoopFile(selectedProjectID, task.id);
|
||||
} else {
|
||||
await deleteScheduledTask(selectedProjectID, task.id);
|
||||
}
|
||||
await reloadTasks(selectedProjectID, { silent: true });
|
||||
toast.success(t('sessions.scheduledTasks.dialog.toast.deleted'));
|
||||
} catch (error) {
|
||||
@@ -348,6 +358,24 @@ export function ScheduledTasksDialog() {
|
||||
}
|
||||
}, [selectedProjectID, reloadTasks, t]);
|
||||
|
||||
const handleEditTask = React.useCallback((task: ScheduledTask) => {
|
||||
if (!task.loopFile) {
|
||||
setEditorTask(task);
|
||||
setEditorOpen(true);
|
||||
return;
|
||||
}
|
||||
if (!selectedProject?.path) {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
if (isMobile) {
|
||||
useFilesViewTabsStore.getState().setSelectedPath(selectedProject.path, task.loopFile, { allowOutsideRoot: true });
|
||||
useUIStore.getState().setActiveMainTab('files');
|
||||
return;
|
||||
}
|
||||
useUIStore.getState().openContextFile(selectedProject.path, task.loopFile);
|
||||
}, [isMobile, selectedProject?.path, setOpen]);
|
||||
|
||||
const handleRunNow = React.useCallback(async (task: ScheduledTask) => {
|
||||
if (!selectedProjectID) {
|
||||
return;
|
||||
@@ -533,11 +561,8 @@ export function ScheduledTasksDialog() {
|
||||
className={cn(
|
||||
'inline-flex cursor-pointer items-center gap-2 typography-micro font-medium',
|
||||
task.enabled ? 'text-foreground' : 'text-muted-foreground',
|
||||
(isBusy || task.loopFile) && 'cursor-not-allowed opacity-50',
|
||||
isBusy && 'cursor-not-allowed opacity-50',
|
||||
)}
|
||||
title={task.loopFile
|
||||
? t('sessions.scheduledTasks.dialog.loopFile.toggleDisabled')
|
||||
: undefined}
|
||||
>
|
||||
<Checkbox
|
||||
checked={task.enabled}
|
||||
@@ -545,7 +570,7 @@ export function ScheduledTasksDialog() {
|
||||
ariaLabel={task.enabled
|
||||
? t('sessions.scheduledTasks.dialog.taskToggle.pauseAria', { taskName: task.name })
|
||||
: t('sessions.scheduledTasks.dialog.taskToggle.enableAria', { taskName: task.name })}
|
||||
disabled={isBusy || Boolean(task.loopFile)}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
{task.enabled ? t('sessions.scheduledTasks.dialog.taskToggle.enabled') : t('sessions.scheduledTasks.dialog.taskToggle.paused')}
|
||||
</label>
|
||||
@@ -562,14 +587,8 @@ export function ScheduledTasksDialog() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setEditorTask(task);
|
||||
setEditorOpen(true);
|
||||
}}
|
||||
disabled={isBusy || Boolean(task.loopFile)}
|
||||
title={task.loopFile
|
||||
? t('sessions.scheduledTasks.dialog.loopFile.actionsDisabled')
|
||||
: undefined}
|
||||
onClick={() => handleEditTask(task)}
|
||||
disabled={isBusy}
|
||||
aria-label={t('sessions.scheduledTasks.dialog.actions.editAria', { taskName: task.name })}
|
||||
>
|
||||
<Icon name="edit-2" className="h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.edit')}
|
||||
@@ -578,10 +597,7 @@ export function ScheduledTasksDialog() {
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => void handleDeleteTask(task)}
|
||||
disabled={isBusy || Boolean(task.loopFile)}
|
||||
title={task.loopFile
|
||||
? t('sessions.scheduledTasks.dialog.loopFile.actionsDisabled')
|
||||
: undefined}
|
||||
disabled={isBusy}
|
||||
aria-label={t('sessions.scheduledTasks.dialog.actions.deleteAria', { taskName: task.name })}
|
||||
>
|
||||
<Icon name="delete-bin" className="h-4 w-4" />
|
||||
|
||||
@@ -75,6 +75,8 @@ import { useOpenInAppsStore } from '@/stores/useOpenInAppsStore';
|
||||
import { eventMatchesShortcut, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { sessionEvents } from '@/lib/sessionEvents';
|
||||
import { syncScheduledTaskLoops } from '@/lib/scheduledTasksApi';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
|
||||
type FileNode = {
|
||||
name: string;
|
||||
@@ -1668,6 +1670,16 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
sessionEvents.requestGitRefresh({ directory: root, paths: [relativePath] });
|
||||
}
|
||||
}
|
||||
if (root && /(?:^|\/)\.agents\/loops\/[^/]+\.md$/i.test(normalizePath(selectedFile.path))) {
|
||||
const project = useProjectsStore.getState().projects.find((entry) => normalizePath(entry.path) === normalizePath(root));
|
||||
if (project) {
|
||||
try {
|
||||
await syncScheduledTaskLoops(project.id);
|
||||
} catch {
|
||||
toast.error(t('sessions.scheduledTasks.dialog.toast.updateFailed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedFile?.path && isDrawioFile(selectedFile.path)) {
|
||||
diagramXmlRef.current = draftContent;
|
||||
diagramSavedXmlRef.current = draftContent;
|
||||
|
||||
Reference in New Issue
Block a user