@lobehub/lobehub 2.0.0-next.275 → 2.0.0-next.277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/changelog/v1.json +14 -0
- package/locales/en-US/setting.json +11 -0
- package/locales/zh-CN/setting.json +11 -0
- package/package.json +1 -1
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/BatchCreateAgents/index.tsx +2 -2
- package/packages/builtin-tool-group-agent-builder/src/client/Inspector/UpdateGroup/index.tsx +56 -56
- package/packages/builtin-tool-group-agent-builder/src/client/Render/BatchCreateAgents.tsx +3 -2
- package/packages/builtin-tool-group-agent-builder/src/executor.ts +2 -1
- package/packages/types/src/agentCronJob/index.ts +19 -23
- package/packages/types/src/serverConfig.ts +1 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/Actions.tsx +31 -0
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +10 -6
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/index.tsx +7 -11
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/useDropdownMenu.tsx +102 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/CronConfig.ts +179 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +111 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobHeader.tsx +45 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobSaveButton.tsx +31 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +213 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +186 -344
- package/src/app/[variants]/(main)/agent/features/Conversation/index.tsx +8 -2
- package/src/app/[variants]/(main)/agent/features/Portal/_layout/Mobile.tsx +1 -0
- package/src/app/[variants]/(main)/agent/features/Portal/features/Portal.tsx +4 -2
- package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/index.tsx +42 -97
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +4 -20
- package/src/app/[variants]/(main)/community/features/UserAvatar/index.tsx +15 -5
- package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +1 -6
- package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +1 -0
- package/src/app/[variants]/(main)/group/features/Portal/features/Portal.tsx +4 -2
- package/src/hooks/useYamlArguments.ts +11 -8
- package/src/server/globalConfig/index.ts +1 -0
- package/src/services/chatGroup/index.ts +1 -4
- package/src/store/serverConfig/selectors.ts +1 -0
|
@@ -1,39 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
|
4
3
|
import { EDITOR_DEBOUNCE_TIME } from '@lobechat/const';
|
|
5
|
-
import {
|
|
6
|
-
ReactCodePlugin,
|
|
7
|
-
ReactCodemirrorPlugin,
|
|
8
|
-
ReactHRPlugin,
|
|
9
|
-
ReactLinkPlugin,
|
|
10
|
-
ReactListPlugin,
|
|
11
|
-
ReactMathPlugin,
|
|
12
|
-
ReactTablePlugin,
|
|
13
|
-
} from '@lobehub/editor';
|
|
14
|
-
import { Editor, useEditor, useEditorState } from '@lobehub/editor/react';
|
|
15
|
-
import { ActionIcon, Flexbox, Icon, Input, Tag, Text } from '@lobehub/ui';
|
|
4
|
+
import { ActionIcon, Flexbox } from '@lobehub/ui';
|
|
16
5
|
import { useDebounceFn } from 'ahooks';
|
|
17
|
-
import { App,
|
|
6
|
+
import { App, Empty, message } from 'antd';
|
|
18
7
|
import dayjs, { type Dayjs } from 'dayjs';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
memo,
|
|
22
|
-
useCallback,
|
|
23
|
-
useEffect,
|
|
24
|
-
useMemo,
|
|
25
|
-
useRef,
|
|
26
|
-
useState,
|
|
27
|
-
useSyncExternalStore,
|
|
28
|
-
} from 'react';
|
|
8
|
+
import { Trash2 } from 'lucide-react';
|
|
9
|
+
import { memo, useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
29
10
|
import { useTranslation } from 'react-i18next';
|
|
30
11
|
import { useParams } from 'react-router-dom';
|
|
31
12
|
import useSWR from 'swr';
|
|
32
13
|
|
|
33
14
|
import AutoSaveHint from '@/components/Editor/AutoSaveHint';
|
|
34
15
|
import Loading from '@/components/Loading/BrandTextLoading';
|
|
35
|
-
import type {
|
|
36
|
-
import { InlineToolbar } from '@/features/EditorCanvas';
|
|
16
|
+
import type { UpdateAgentCronJobData } from '@/database/schemas/agentCronJob';
|
|
37
17
|
import NavHeader from '@/features/NavHeader';
|
|
38
18
|
import WideScreenContainer from '@/features/WideScreenContainer';
|
|
39
19
|
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
|
@@ -43,18 +23,27 @@ import { agentCronJobService } from '@/services/agentCronJob';
|
|
|
43
23
|
import { topicService } from '@/services/topic';
|
|
44
24
|
import { useAgentStore } from '@/store/agent';
|
|
45
25
|
import { useChatStore } from '@/store/chat';
|
|
26
|
+
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
46
27
|
import { useUserStore } from '@/store/user';
|
|
47
28
|
import { labPreferSelectors } from '@/store/user/selectors';
|
|
48
29
|
|
|
30
|
+
import { type ScheduleType, buildCronPattern, parseCronPattern } from './CronConfig';
|
|
31
|
+
import CronJobContentEditor from './features/CronJobContentEditor';
|
|
32
|
+
import CronJobHeader from './features/CronJobHeader';
|
|
33
|
+
import CronJobSaveButton from './features/CronJobSaveButton';
|
|
34
|
+
import CronJobScheduleConfig from './features/CronJobScheduleConfig';
|
|
35
|
+
|
|
49
36
|
interface CronJobDraft {
|
|
50
37
|
content: string;
|
|
51
38
|
cronPattern: string;
|
|
52
39
|
description: string;
|
|
40
|
+
hourlyInterval?: number; // For hourly: interval in hours (1, 2, 6, 12)
|
|
53
41
|
maxExecutions?: number | null;
|
|
54
|
-
maxExecutionsPerDay?: number | null;
|
|
55
42
|
name: string;
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
scheduleType: ScheduleType;
|
|
44
|
+
timezone: string;
|
|
45
|
+
triggerTime: Dayjs; // Trigger time (HH:mm)
|
|
46
|
+
weekdays: number[]; // For weekly: selected days
|
|
58
47
|
}
|
|
59
48
|
|
|
60
49
|
type AutoSaveStatus = 'idle' | 'saving' | 'saved';
|
|
@@ -85,52 +74,6 @@ const AutoSaveHintSlot = memo(() => {
|
|
|
85
74
|
return <AutoSaveHint lastUpdatedTime={lastUpdatedTime} saveStatus={status} />;
|
|
86
75
|
});
|
|
87
76
|
|
|
88
|
-
// Standard cron format: minute hour day month weekday
|
|
89
|
-
const CRON_PATTERNS = [
|
|
90
|
-
{ label: 'agentCronJobs.interval.30min', value: '*/30 * * * *' },
|
|
91
|
-
{ label: 'agentCronJobs.interval.1hour', value: '0 * * * *' },
|
|
92
|
-
{ label: 'agentCronJobs.interval.2hours', value: '0 */2 * * *' },
|
|
93
|
-
{ label: 'agentCronJobs.interval.6hours', value: '0 */6 * * *' },
|
|
94
|
-
{ label: 'agentCronJobs.interval.12hours', value: '0 */12 * * *' },
|
|
95
|
-
{ label: 'agentCronJobs.interval.daily', value: '0 0 * * *' },
|
|
96
|
-
{ label: 'agentCronJobs.interval.weekly', value: '0 0 * * 0' },
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const WEEKDAY_OPTIONS = [
|
|
100
|
-
{ label: 'Mon', value: 1 },
|
|
101
|
-
{ label: 'Tue', value: 2 },
|
|
102
|
-
{ label: 'Wed', value: 3 },
|
|
103
|
-
{ label: 'Thu', value: 4 },
|
|
104
|
-
{ label: 'Fri', value: 5 },
|
|
105
|
-
{ label: 'Sat', value: 6 },
|
|
106
|
-
{ label: 'Sun', value: 0 },
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
const WEEKDAY_LABELS: Record<number, string> = {
|
|
110
|
-
0: 'Sunday',
|
|
111
|
-
1: 'Monday',
|
|
112
|
-
2: 'Tuesday',
|
|
113
|
-
3: 'Wednesday',
|
|
114
|
-
4: 'Thursday',
|
|
115
|
-
5: 'Friday',
|
|
116
|
-
6: 'Saturday',
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const getIntervalText = (cronPattern: string) => {
|
|
120
|
-
// Standard cron format mapping
|
|
121
|
-
const intervalMap: Record<string, string> = {
|
|
122
|
-
'*/30 * * * *': 'agentCronJobs.interval.30min',
|
|
123
|
-
'0 * * * *': 'agentCronJobs.interval.1hour',
|
|
124
|
-
'0 */12 * * *': 'agentCronJobs.interval.12hours',
|
|
125
|
-
'0 */2 * * *': 'agentCronJobs.interval.2hours',
|
|
126
|
-
'0 */6 * * *': 'agentCronJobs.interval.6hours',
|
|
127
|
-
'0 0 * * *': 'agentCronJobs.interval.daily',
|
|
128
|
-
'0 0 * * 0': 'agentCronJobs.interval.weekly',
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return intervalMap[cronPattern] || cronPattern;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
77
|
const resolveDate = (value?: Date | string | null) => {
|
|
135
78
|
if (!value) return null;
|
|
136
79
|
return typeof value === 'string' ? new Date(value) : value;
|
|
@@ -141,15 +84,14 @@ const CronJobDetailPage = memo(() => {
|
|
|
141
84
|
const { aid, cronId } = useParams<{ aid?: string; cronId?: string }>();
|
|
142
85
|
const router = useQueryRoute();
|
|
143
86
|
const { modal } = App.useApp();
|
|
144
|
-
const editor = useEditor();
|
|
145
|
-
const editorState = useEditorState(editor);
|
|
146
87
|
const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
|
|
147
|
-
const
|
|
88
|
+
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
|
|
89
|
+
|
|
90
|
+
const isNewJob = cronId === 'new';
|
|
148
91
|
|
|
149
92
|
const [draft, setDraft] = useState<CronJobDraft | null>(null);
|
|
150
93
|
const draftRef = useRef<CronJobDraft | null>(null);
|
|
151
94
|
const contentRef = useRef('');
|
|
152
|
-
const pendingContentRef = useRef<string | null>(null);
|
|
153
95
|
const pendingSaveRef = useRef(false);
|
|
154
96
|
const initializedIdRef = useRef<string | null>(null);
|
|
155
97
|
const readyRef = useRef(false);
|
|
@@ -170,9 +112,9 @@ const CronJobDetailPage = memo(() => {
|
|
|
170
112
|
const cronListAgentId = activeAgentId || aid;
|
|
171
113
|
|
|
172
114
|
const { data: cronJob, isLoading } = useSWR(
|
|
173
|
-
|
|
115
|
+
enableBusinessFeatures && cronId && !isNewJob ? ['cronJob', cronId] : null,
|
|
174
116
|
async () => {
|
|
175
|
-
if (!cronId) return null;
|
|
117
|
+
if (!cronId || isNewJob) return null;
|
|
176
118
|
const result = await agentCronJobService.getById(cronId);
|
|
177
119
|
return result.success ? result.data : null;
|
|
178
120
|
},
|
|
@@ -184,74 +126,28 @@ const CronJobDetailPage = memo(() => {
|
|
|
184
126
|
},
|
|
185
127
|
);
|
|
186
128
|
|
|
187
|
-
const resolvedCronPattern = draft ? draft.cronPattern : cronJob?.cronPattern;
|
|
188
|
-
const resolvedWeekdays = draft ? draft.weekdays : cronJob?.executionConditions?.weekdays || [];
|
|
189
|
-
const resolvedTimeRange = draft
|
|
190
|
-
? draft.timeRange
|
|
191
|
-
: cronJob?.executionConditions?.timeRange
|
|
192
|
-
? [
|
|
193
|
-
dayjs(cronJob.executionConditions.timeRange.start, 'HH:mm'),
|
|
194
|
-
dayjs(cronJob.executionConditions.timeRange.end, 'HH:mm'),
|
|
195
|
-
]
|
|
196
|
-
: undefined;
|
|
197
|
-
|
|
198
|
-
const summaryTags = useMemo(() => {
|
|
199
|
-
const tags: Array<{ key: string; label: string }> = [];
|
|
200
|
-
|
|
201
|
-
if (resolvedCronPattern) {
|
|
202
|
-
tags.push({
|
|
203
|
-
key: 'interval',
|
|
204
|
-
label: t(getIntervalText(resolvedCronPattern) as any),
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (resolvedWeekdays.length > 0) {
|
|
209
|
-
tags.push({
|
|
210
|
-
key: 'weekdays',
|
|
211
|
-
label: resolvedWeekdays.map((day) => WEEKDAY_LABELS[day]).join(', '),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (resolvedTimeRange && resolvedTimeRange.length === 2) {
|
|
216
|
-
tags.push({
|
|
217
|
-
key: 'timeRange',
|
|
218
|
-
label: `${resolvedTimeRange[0].format('HH:mm')} - ${resolvedTimeRange[1].format('HH:mm')}`,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return tags;
|
|
223
|
-
}, [resolvedCronPattern, resolvedTimeRange, resolvedWeekdays, t]);
|
|
224
|
-
|
|
225
129
|
const buildUpdateData = useCallback(
|
|
226
130
|
(snapshot: CronJobDraft | null, content: string): UpdateAgentCronJobData | null => {
|
|
227
131
|
if (!snapshot) return null;
|
|
228
132
|
if (!snapshot.content) return null;
|
|
229
133
|
if (!snapshot.name) return null;
|
|
230
134
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (snapshot.weekdays && snapshot.weekdays.length > 0) {
|
|
240
|
-
executionConditions.weekdays = snapshot.weekdays;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (snapshot.maxExecutionsPerDay) {
|
|
244
|
-
executionConditions.maxExecutionsPerDay = snapshot.maxExecutionsPerDay;
|
|
245
|
-
}
|
|
135
|
+
// Build cron pattern from schedule configuration
|
|
136
|
+
const cronPattern = buildCronPattern(
|
|
137
|
+
snapshot.scheduleType,
|
|
138
|
+
snapshot.triggerTime,
|
|
139
|
+
snapshot.hourlyInterval,
|
|
140
|
+
snapshot.weekdays,
|
|
141
|
+
);
|
|
246
142
|
|
|
247
143
|
return {
|
|
248
144
|
content,
|
|
249
|
-
cronPattern
|
|
145
|
+
cronPattern,
|
|
250
146
|
description: snapshot.description?.trim() || null,
|
|
251
|
-
executionConditions:
|
|
252
|
-
Object.keys(executionConditions).length > 0 ? executionConditions : null,
|
|
147
|
+
executionConditions: null, // No longer using executionConditions for time/weekdays
|
|
253
148
|
maxExecutions: snapshot.maxExecutions ?? null,
|
|
254
149
|
name: snapshot.name?.trim() || null,
|
|
150
|
+
timezone: snapshot.timezone,
|
|
255
151
|
};
|
|
256
152
|
},
|
|
257
153
|
[],
|
|
@@ -272,11 +168,11 @@ const CronJobDetailPage = memo(() => {
|
|
|
272
168
|
(current) =>
|
|
273
169
|
current
|
|
274
170
|
? {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
171
|
+
...current,
|
|
172
|
+
...payload,
|
|
173
|
+
executionConditions: payload.executionConditions ?? null,
|
|
174
|
+
...(updatedAt ? { updatedAt } : null),
|
|
175
|
+
}
|
|
280
176
|
: current,
|
|
281
177
|
false,
|
|
282
178
|
);
|
|
@@ -291,6 +187,7 @@ const CronJobDetailPage = memo(() => {
|
|
|
291
187
|
|
|
292
188
|
const { run: debouncedSave, cancel: cancelDebouncedSave } = useDebounceFn(
|
|
293
189
|
async () => {
|
|
190
|
+
if (isNewJob) return; // Don't auto-save new jobs
|
|
294
191
|
if (!cronId || initializedIdRef.current !== cronId) return;
|
|
295
192
|
const payload = buildUpdateData(draftRef.current, contentRef.current);
|
|
296
193
|
if (!payload) return;
|
|
@@ -323,6 +220,7 @@ const CronJobDetailPage = memo(() => {
|
|
|
323
220
|
}, [cancelDebouncedSave, cronId]);
|
|
324
221
|
|
|
325
222
|
const scheduleSave = useCallback(() => {
|
|
223
|
+
if (isNewJob) return; // Don't auto-save new jobs
|
|
326
224
|
if (!readyRef.current || !draftRef.current) {
|
|
327
225
|
pendingSaveRef.current = true;
|
|
328
226
|
return;
|
|
@@ -330,15 +228,16 @@ const CronJobDetailPage = memo(() => {
|
|
|
330
228
|
isDirtyRef.current = true;
|
|
331
229
|
setAutoSaveState({ status: 'saving' });
|
|
332
230
|
debouncedSave();
|
|
333
|
-
}, [debouncedSave]);
|
|
231
|
+
}, [debouncedSave, isNewJob]);
|
|
334
232
|
|
|
335
233
|
const flushPendingSave = useCallback(() => {
|
|
234
|
+
if (isNewJob) return; // Don't auto-save new jobs
|
|
336
235
|
if (!pendingSaveRef.current || !draftRef.current) return;
|
|
337
236
|
pendingSaveRef.current = false;
|
|
338
237
|
isDirtyRef.current = true;
|
|
339
238
|
setAutoSaveState({ status: 'saving' });
|
|
340
239
|
debouncedSave();
|
|
341
|
-
}, [debouncedSave]);
|
|
240
|
+
}, [debouncedSave, isNewJob]);
|
|
342
241
|
|
|
343
242
|
const updateDraft = useCallback(
|
|
344
243
|
(patch: Partial<CronJobDraft>) => {
|
|
@@ -353,14 +252,13 @@ const CronJobDetailPage = memo(() => {
|
|
|
353
252
|
[scheduleSave],
|
|
354
253
|
);
|
|
355
254
|
|
|
356
|
-
const handleContentChange = useCallback(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}, [editor, editorReady, enableRichRender, scheduleSave]);
|
|
255
|
+
const handleContentChange = useCallback(
|
|
256
|
+
(content: string) => {
|
|
257
|
+
contentRef.current = content;
|
|
258
|
+
updateDraft({ content });
|
|
259
|
+
},
|
|
260
|
+
[updateDraft],
|
|
261
|
+
);
|
|
364
262
|
|
|
365
263
|
const handleToggleEnabled = useCallback(
|
|
366
264
|
async (enabled: boolean) => {
|
|
@@ -378,12 +276,15 @@ const CronJobDetailPage = memo(() => {
|
|
|
378
276
|
[cronId, mutate, refreshCronList],
|
|
379
277
|
);
|
|
380
278
|
|
|
381
|
-
const handleDeleteCronJob = useCallback(() => {
|
|
279
|
+
const handleDeleteCronJob = useCallback(async () => {
|
|
382
280
|
if (!cronId) return;
|
|
383
281
|
|
|
384
282
|
modal.confirm({
|
|
283
|
+
cancelText: t('cancel', { ns: 'common' }),
|
|
385
284
|
centered: true,
|
|
285
|
+
content: t('agentCronJobs.confirmDeleteCronJob' as any),
|
|
386
286
|
okButtonProps: { danger: true },
|
|
287
|
+
okText: t('ok', { ns: 'common' }),
|
|
387
288
|
onOk: async () => {
|
|
388
289
|
try {
|
|
389
290
|
let topicIds: string[] = [];
|
|
@@ -416,10 +317,82 @@ const CronJobDetailPage = memo(() => {
|
|
|
416
317
|
message.error('Failed to delete scheduled task');
|
|
417
318
|
}
|
|
418
319
|
},
|
|
419
|
-
title: t('agentCronJobs.
|
|
320
|
+
title: t('agentCronJobs.deleteCronJob' as any),
|
|
420
321
|
});
|
|
421
322
|
}, [activeTopicId, cronId, cronListAgentId, modal, refreshTopic, router, switchTopic, t]);
|
|
422
323
|
|
|
324
|
+
const handleSaveNewJob = useCallback(async () => {
|
|
325
|
+
if (!aid) {
|
|
326
|
+
message.error('Agent ID is required');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const payload = buildUpdateData(draftRef.current, contentRef.current);
|
|
331
|
+
if (!payload) {
|
|
332
|
+
message.error('Please fill in all required fields');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!payload.content || !payload.name || !payload.cronPattern) {
|
|
337
|
+
message.error('Name and content are required');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
setAutoSaveState({ status: 'saving' });
|
|
342
|
+
try {
|
|
343
|
+
const result = await agentCronJobService.create({
|
|
344
|
+
agentId: aid,
|
|
345
|
+
content: payload.content,
|
|
346
|
+
cronPattern: payload.cronPattern,
|
|
347
|
+
description: payload.description,
|
|
348
|
+
enabled: true,
|
|
349
|
+
executionConditions: payload.executionConditions,
|
|
350
|
+
maxExecutions: payload.maxExecutions,
|
|
351
|
+
name: payload.name,
|
|
352
|
+
timezone: payload.timezone,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (result.success && result.data) {
|
|
356
|
+
setAutoSaveState({ lastUpdatedTime: new Date(), status: 'saved' });
|
|
357
|
+
message.success('Scheduled task created successfully');
|
|
358
|
+
refreshCronList();
|
|
359
|
+
// Navigate to the newly created job
|
|
360
|
+
router.push(`/agent/${aid}/cron/${result.data.id}`);
|
|
361
|
+
} else {
|
|
362
|
+
throw new Error('Failed to create job');
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error('Failed to create cron job:', error);
|
|
366
|
+
setAutoSaveState({ status: 'idle' });
|
|
367
|
+
message.error('Failed to create scheduled task');
|
|
368
|
+
}
|
|
369
|
+
}, [aid, buildUpdateData, refreshCronList, router]);
|
|
370
|
+
|
|
371
|
+
// Initialize draft for new jobs
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (!isNewJob || draft) return;
|
|
374
|
+
|
|
375
|
+
// Get browser timezone
|
|
376
|
+
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
377
|
+
|
|
378
|
+
const defaultDraft: CronJobDraft = {
|
|
379
|
+
content: '',
|
|
380
|
+
cronPattern: '0 0 * * *', // Default: daily at midnight
|
|
381
|
+
description: '',
|
|
382
|
+
maxExecutions: null,
|
|
383
|
+
name: '',
|
|
384
|
+
scheduleType: 'daily',
|
|
385
|
+
timezone: browserTimezone,
|
|
386
|
+
triggerTime: dayjs().hour(0).minute(0),
|
|
387
|
+
weekdays: [0, 1, 2, 3, 4, 5, 6],
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
setDraft(defaultDraft);
|
|
391
|
+
draftRef.current = defaultDraft;
|
|
392
|
+
contentRef.current = '';
|
|
393
|
+
readyRef.current = true;
|
|
394
|
+
}, [isNewJob, draft]);
|
|
395
|
+
|
|
423
396
|
useEffect(() => {
|
|
424
397
|
if (!cronJob) return;
|
|
425
398
|
const cronUpdatedAt = cronJob.updatedAt ? new Date(cronJob.updatedAt).toISOString() : null;
|
|
@@ -434,230 +407,99 @@ const CronJobDetailPage = memo(() => {
|
|
|
434
407
|
readyRef.current = false;
|
|
435
408
|
lastSavedNameRef.current = cronJob.name ?? null;
|
|
436
409
|
|
|
410
|
+
// Parse cron pattern to extract schedule configuration
|
|
411
|
+
const parsed = parseCronPattern(cronJob.cronPattern);
|
|
412
|
+
|
|
437
413
|
const nextDraft: CronJobDraft = {
|
|
438
414
|
content: cronJob.content || '',
|
|
439
415
|
cronPattern: cronJob.cronPattern,
|
|
440
416
|
description: cronJob.description || '',
|
|
417
|
+
hourlyInterval: parsed.hourlyInterval,
|
|
441
418
|
maxExecutions: cronJob.maxExecutions ?? null,
|
|
442
|
-
maxExecutionsPerDay: cronJob.executionConditions?.maxExecutionsPerDay ?? null,
|
|
443
419
|
name: cronJob.name || '',
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
420
|
+
scheduleType: parsed.scheduleType,
|
|
421
|
+
timezone: cronJob.timezone || 'UTC',
|
|
422
|
+
triggerTime: dayjs().hour(parsed.triggerHour).minute(parsed.triggerMinute),
|
|
423
|
+
weekdays:
|
|
424
|
+
parsed.scheduleType === 'weekly' && parsed.weekdays
|
|
425
|
+
? parsed.weekdays
|
|
426
|
+
: [0, 1, 2, 3, 4, 5, 6], // Default: all days for weekly
|
|
451
427
|
};
|
|
452
428
|
|
|
453
429
|
setDraft(nextDraft);
|
|
454
430
|
draftRef.current = nextDraft;
|
|
455
431
|
|
|
456
432
|
contentRef.current = nextDraft.content;
|
|
457
|
-
pendingContentRef.current = nextDraft.content;
|
|
458
433
|
|
|
459
434
|
setAutoSaveState({
|
|
460
435
|
lastUpdatedTime: resolveDate(cronJob.updatedAt),
|
|
461
436
|
status: 'saved',
|
|
462
437
|
});
|
|
463
438
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', nextDraft.content);
|
|
468
|
-
}, 100);
|
|
469
|
-
pendingContentRef.current = null;
|
|
470
|
-
readyRef.current = true;
|
|
471
|
-
flushPendingSave();
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error('[CronJobDetailPage] Failed to init editor content:', error);
|
|
474
|
-
setTimeout(() => {
|
|
475
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', nextDraft.content);
|
|
476
|
-
}, 100);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}, [cronJob, editor, editorReady, enableRichRender]);
|
|
480
|
-
|
|
481
|
-
useEffect(() => {
|
|
482
|
-
if (!editorReady || !editor || pendingContentRef.current === null) return;
|
|
483
|
-
try {
|
|
484
|
-
setTimeout(() => {
|
|
485
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', pendingContentRef.current);
|
|
486
|
-
}, 100);
|
|
487
|
-
pendingContentRef.current = null;
|
|
488
|
-
readyRef.current = true;
|
|
489
|
-
flushPendingSave();
|
|
490
|
-
} catch (error) {
|
|
491
|
-
console.error('[CronJobDetailPage] Failed to init editor content:', error);
|
|
492
|
-
setTimeout(() => {
|
|
493
|
-
console.log('setDocument timeout', pendingContentRef.current);
|
|
494
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', pendingContentRef.current);
|
|
495
|
-
}, 100);
|
|
496
|
-
}
|
|
497
|
-
}, [editor, editorReady, enableRichRender]);
|
|
439
|
+
readyRef.current = true;
|
|
440
|
+
flushPendingSave();
|
|
441
|
+
}, [cronJob, flushPendingSave]);
|
|
498
442
|
|
|
499
|
-
if (!
|
|
443
|
+
if (!enableBusinessFeatures) {
|
|
500
444
|
return null;
|
|
501
445
|
}
|
|
502
446
|
|
|
503
447
|
return (
|
|
504
448
|
<Flexbox flex={1} height={'100%'}>
|
|
505
|
-
<NavHeader
|
|
449
|
+
<NavHeader
|
|
450
|
+
left={!isNewJob ? <AutoSaveHintSlot /> : undefined}
|
|
451
|
+
right={
|
|
452
|
+
!isNewJob ? (
|
|
453
|
+
<ActionIcon
|
|
454
|
+
icon={Trash2}
|
|
455
|
+
onClick={handleDeleteCronJob}
|
|
456
|
+
title={t('delete', { ns: 'common' })}
|
|
457
|
+
/>
|
|
458
|
+
) : undefined
|
|
459
|
+
}
|
|
460
|
+
/>
|
|
506
461
|
<Flexbox flex={1} style={{ overflowY: 'auto' }}>
|
|
507
462
|
<WideScreenContainer paddingBlock={16}>
|
|
508
463
|
{isLoading && <Loading debugId="CronJobDetailPage" />}
|
|
509
|
-
{!isLoading && !cronJob && (
|
|
464
|
+
{!isLoading && !cronJob && !isNewJob && (
|
|
510
465
|
<Empty
|
|
511
466
|
description={t('agentCronJobs.empty.description')}
|
|
512
467
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
513
468
|
/>
|
|
514
469
|
)}
|
|
515
|
-
{!isLoading && cronJob && (
|
|
470
|
+
{!isLoading && (cronJob || isNewJob) && draft && (
|
|
516
471
|
<Flexbox gap={24}>
|
|
517
|
-
<
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
key={cronJob?.id ?? 'cron-switch'}
|
|
549
|
-
onChange={handleToggleEnabled}
|
|
550
|
-
size="small"
|
|
551
|
-
/>
|
|
552
|
-
</Flexbox>
|
|
553
|
-
</Flexbox>
|
|
554
|
-
|
|
555
|
-
<Card size="small" style={{ borderRadius: 12 }} styles={{ body: { padding: 12 } }}>
|
|
556
|
-
<Flexbox gap={12}>
|
|
557
|
-
{summaryTags.length > 0 && (
|
|
558
|
-
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
|
|
559
|
-
{summaryTags.map((tag) => (
|
|
560
|
-
<Tag key={tag.key} variant={'filled'}>
|
|
561
|
-
{tag.label}
|
|
562
|
-
</Tag>
|
|
563
|
-
))}
|
|
564
|
-
</Flexbox>
|
|
565
|
-
)}
|
|
566
|
-
|
|
567
|
-
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
|
|
568
|
-
<Tag variant={'borderless'}>{t('agentCronJobs.schedule')}</Tag>
|
|
569
|
-
<Select
|
|
570
|
-
onChange={(value) => updateDraft({ cronPattern: value })}
|
|
571
|
-
options={CRON_PATTERNS.map((pattern) => ({
|
|
572
|
-
label: t(pattern.label as any),
|
|
573
|
-
value: pattern.value,
|
|
574
|
-
}))}
|
|
575
|
-
size="small"
|
|
576
|
-
style={{ minWidth: 160 }}
|
|
577
|
-
value={draft?.cronPattern ?? cronJob.cronPattern}
|
|
578
|
-
/>
|
|
579
|
-
<TimePicker.RangePicker
|
|
580
|
-
format="HH:mm"
|
|
581
|
-
onChange={(value) =>
|
|
582
|
-
updateDraft({
|
|
583
|
-
timeRange:
|
|
584
|
-
value && value.length === 2
|
|
585
|
-
? [value[0] as Dayjs, value[1] as Dayjs]
|
|
586
|
-
: undefined,
|
|
587
|
-
})
|
|
588
|
-
}
|
|
589
|
-
placeholder={[
|
|
590
|
-
t('agentCronJobs.form.timeRange.start'),
|
|
591
|
-
t('agentCronJobs.form.timeRange.end'),
|
|
592
|
-
]}
|
|
593
|
-
size="small"
|
|
594
|
-
value={
|
|
595
|
-
draft?.timeRange ??
|
|
596
|
-
(resolvedTimeRange as [Dayjs, Dayjs] | undefined) ??
|
|
597
|
-
null
|
|
598
|
-
}
|
|
599
|
-
/>
|
|
600
|
-
<Checkbox.Group
|
|
601
|
-
onChange={(values) => updateDraft({ weekdays: values as number[] })}
|
|
602
|
-
options={WEEKDAY_OPTIONS}
|
|
603
|
-
style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}
|
|
604
|
-
value={draft?.weekdays ?? resolvedWeekdays}
|
|
605
|
-
/>
|
|
606
|
-
</Flexbox>
|
|
607
|
-
|
|
608
|
-
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
|
|
609
|
-
<Tag variant={'borderless'}>{t('agentCronJobs.maxExecutions')}</Tag>
|
|
610
|
-
<InputNumber
|
|
611
|
-
min={1}
|
|
612
|
-
onChange={(value) => updateDraft({ maxExecutions: value ?? null })}
|
|
613
|
-
placeholder={t('agentCronJobs.form.maxExecutions.placeholder')}
|
|
614
|
-
size="small"
|
|
615
|
-
style={{ width: 160 }}
|
|
616
|
-
value={draft?.maxExecutions ?? cronJob.maxExecutions ?? null}
|
|
617
|
-
/>
|
|
618
|
-
</Flexbox>
|
|
619
|
-
</Flexbox>
|
|
620
|
-
</Card>
|
|
621
|
-
|
|
622
|
-
<Flexbox gap={12}>
|
|
623
|
-
<Flexbox align="center" gap={6} horizontal>
|
|
624
|
-
<Icon icon={Clock} size={16} />
|
|
625
|
-
<Text style={{ fontWeight: 600 }}>{t('agentCronJobs.content')}</Text>
|
|
626
|
-
</Flexbox>
|
|
627
|
-
<Card
|
|
628
|
-
size="small"
|
|
629
|
-
style={{ borderRadius: 12, overflow: 'hidden' }}
|
|
630
|
-
styles={{ body: { padding: 0 } }}
|
|
631
|
-
>
|
|
632
|
-
{enableRichRender && <InlineToolbar editor={editor} editorState={editorState} />}
|
|
633
|
-
<Flexbox padding={16} style={{ minHeight: 220 }}>
|
|
634
|
-
<Editor
|
|
635
|
-
content={''}
|
|
636
|
-
editor={editor}
|
|
637
|
-
lineEmptyPlaceholder={t('agentCronJobs.form.content.placeholder')}
|
|
638
|
-
onInit={() => setEditorReady(true)}
|
|
639
|
-
onTextChange={handleContentChange}
|
|
640
|
-
placeholder={t('agentCronJobs.form.content.placeholder')}
|
|
641
|
-
plugins={
|
|
642
|
-
enableRichRender
|
|
643
|
-
? [
|
|
644
|
-
ReactListPlugin,
|
|
645
|
-
ReactCodePlugin,
|
|
646
|
-
ReactCodemirrorPlugin,
|
|
647
|
-
ReactHRPlugin,
|
|
648
|
-
ReactLinkPlugin,
|
|
649
|
-
ReactTablePlugin,
|
|
650
|
-
ReactMathPlugin,
|
|
651
|
-
]
|
|
652
|
-
: undefined
|
|
653
|
-
}
|
|
654
|
-
style={{ paddingBottom: 48 }}
|
|
655
|
-
type={'text'}
|
|
656
|
-
variant={'chat'}
|
|
657
|
-
/>
|
|
658
|
-
</Flexbox>
|
|
659
|
-
</Card>
|
|
660
|
-
</Flexbox>
|
|
472
|
+
<CronJobHeader
|
|
473
|
+
enabled={cronJob?.enabled ?? false}
|
|
474
|
+
isNewJob={isNewJob}
|
|
475
|
+
name={draft.name}
|
|
476
|
+
onNameChange={(name) => updateDraft({ name })}
|
|
477
|
+
onToggleEnabled={handleToggleEnabled}
|
|
478
|
+
/>
|
|
479
|
+
|
|
480
|
+
<CronJobScheduleConfig
|
|
481
|
+
hourlyInterval={draft.hourlyInterval}
|
|
482
|
+
maxExecutions={draft.maxExecutions}
|
|
483
|
+
onScheduleChange={(updates) => updateDraft(updates)}
|
|
484
|
+
scheduleType={draft.scheduleType}
|
|
485
|
+
timezone={draft.timezone}
|
|
486
|
+
triggerTime={draft.triggerTime}
|
|
487
|
+
weekdays={draft.weekdays}
|
|
488
|
+
/>
|
|
489
|
+
|
|
490
|
+
<CronJobContentEditor
|
|
491
|
+
enableRichRender={enableRichRender}
|
|
492
|
+
initialValue={cronJob?.content || ''}
|
|
493
|
+
onChange={handleContentChange}
|
|
494
|
+
/>
|
|
495
|
+
|
|
496
|
+
{isNewJob && (
|
|
497
|
+
<CronJobSaveButton
|
|
498
|
+
disabled={!draft.name || !draft.content}
|
|
499
|
+
loading={false}
|
|
500
|
+
onSave={handleSaveNewJob}
|
|
501
|
+
/>
|
|
502
|
+
)}
|
|
661
503
|
</Flexbox>
|
|
662
504
|
)}
|
|
663
505
|
</WideScreenContainer>
|