@lobehub/lobehub 2.0.0-next.251 → 2.0.0-next.252

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +1 -1
  4. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
  5. package/packages/database/migrations/meta/_journal.json +1 -1
  6. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
  7. package/packages/database/src/models/topic.ts +71 -4
  8. package/packages/database/src/schemas/agentCronJob.ts +1 -2
  9. package/packages/memory-user-memory/src/extractors/context.ts +1 -4
  10. package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
  11. package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
  12. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
  13. package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
  14. package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
  15. package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
  16. package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
  17. package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
  18. package/packages/memory-user-memory/src/types.ts +10 -8
  19. package/packages/types/src/topic/topic.ts +9 -0
  20. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
  21. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
  22. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
  23. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
  24. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
  25. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
  26. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
  27. package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
  28. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
  29. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
  30. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
  31. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
  32. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
  33. package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
  34. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  35. package/src/hooks/useFetchCronTopics.ts +29 -0
  36. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
  37. package/src/hooks/useFetchTopics.ts +4 -1
  38. package/src/locales/default/setting.ts +44 -1
  39. package/src/server/routers/lambda/agentCronJob.ts +367 -0
  40. package/src/server/routers/lambda/image/index.test.ts +2 -2
  41. package/src/server/routers/lambda/index.ts +2 -0
  42. package/src/server/routers/lambda/topic.ts +15 -3
  43. package/src/server/services/aiAgent/index.ts +18 -1
  44. package/src/server/services/memory/userMemory/extract.ts +14 -6
  45. package/src/services/agentCronJob.ts +95 -0
  46. package/src/services/topic/index.ts +1 -0
  47. package/src/store/chat/slices/topic/action.ts +53 -2
  48. package/src/store/chat/slices/topic/initialState.ts +1 -0
  49. package/src/store/chat/slices/topic/selectors.ts +14 -6
  50. package/src/tools/placeholders.ts +1 -4
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, Flexbox, Icon } from '@lobehub/ui';
4
+ import { Badge, Card, Col, Popconfirm, Row, Switch, Typography } from 'antd';
5
+ import dayjs from 'dayjs';
6
+ import { Calendar, Clock, Edit, Trash2 } from 'lucide-react';
7
+ import { memo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
+
12
+ import { useAgentCronJobs } from './hooks/useAgentCronJobs';
13
+
14
+ const { Text } = Typography;
15
+
16
+ interface CronJobCardsProps {
17
+ cronJobs: AgentCronJob[];
18
+ loading?: boolean;
19
+ onDelete: (jobId: string) => void;
20
+ onEdit: (jobId: string) => void;
21
+ }
22
+
23
+ const getIntervalText = (cronPattern: string) => {
24
+ const intervalMap: Record<string, string> = {
25
+ '0 */12 * * *': 'agentCronJobs.interval.12hours',
26
+ '0 */30 * * *': 'agentCronJobs.interval.30min',
27
+ '0 */6 * * *': 'agentCronJobs.interval.6hours',
28
+ '0 0 * * *': 'agentCronJobs.interval.1hour',
29
+ '0 0 0 * *': 'agentCronJobs.interval.daily',
30
+ '0 0 0 * 0': 'agentCronJobs.interval.weekly',
31
+ };
32
+
33
+ return intervalMap[cronPattern] || cronPattern;
34
+ };
35
+
36
+ const getStatusInfo = (job: AgentCronJob) => {
37
+ if (!job.enabled) {
38
+ return { status: 'default' as const, text: 'agentCronJobs.status.disabled' };
39
+ }
40
+
41
+ if (job.remainingExecutions === 0) {
42
+ return { status: 'error' as const, text: 'agentCronJobs.status.depleted' };
43
+ }
44
+
45
+ return { status: 'success' as const, text: 'agentCronJobs.status.enabled' };
46
+ };
47
+
48
+ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onEdit }) => {
49
+ const { t } = useTranslation('setting');
50
+ const { updateCronJob } = useAgentCronJobs();
51
+
52
+ const handleToggleEnabled = async (job: AgentCronJob) => {
53
+ await updateCronJob(job.id, { enabled: !job.enabled });
54
+ };
55
+
56
+ return (
57
+ <Row gutter={[12, 12]}>
58
+ {cronJobs.map((job) => {
59
+ const statusInfo = getStatusInfo(job);
60
+ const intervalText = getIntervalText(job.cronPattern);
61
+
62
+ return (
63
+ <Col key={job.id} lg={8} md={12} xs={24}>
64
+ <Card
65
+ extra={
66
+ <Flexbox align="center" gap={4} horizontal>
67
+ <ActionIcon
68
+ icon={Edit}
69
+ onClick={() => onEdit(job.id)}
70
+ size="small"
71
+ title={t('agentCronJobs.editJob')}
72
+ />
73
+ <Popconfirm
74
+ onConfirm={() => onDelete(job.id)}
75
+ title={t('agentCronJobs.confirmDelete')}
76
+ >
77
+ <ActionIcon icon={Trash2} size="small" title={t('agentCronJobs.deleteJob')} />
78
+ </Popconfirm>
79
+ </Flexbox>
80
+ }
81
+ loading={loading}
82
+ size="small"
83
+ style={{ height: '100%' }}
84
+ styles={{
85
+ actions: { marginTop: 0 },
86
+ body: { paddingBottom: 12, paddingTop: 8 },
87
+ header: { borderBottom: 'none', marginTop: '8px', minHeight: 0, paddingBottom: 0 },
88
+ }}
89
+ title={
90
+ <Flexbox align="center" horizontal justify="space-between">
91
+ <Flexbox align="center" gap={8} horizontal style={{ flex: 1 }}>
92
+ <span
93
+ style={{
94
+ fontSize: '13px',
95
+ fontWeight: 500,
96
+ overflow: 'hidden',
97
+ textOverflow: 'ellipsis',
98
+ whiteSpace: 'nowrap',
99
+ }}
100
+ >
101
+ {job.name || 'Unnamed Task'}
102
+ </span>
103
+ <Badge status={statusInfo.status} />
104
+ </Flexbox>
105
+ <Switch
106
+ checked={job.enabled || false}
107
+ onChange={() => handleToggleEnabled(job)}
108
+ size="small"
109
+ />
110
+ </Flexbox>
111
+ }
112
+ >
113
+ <Flexbox gap={8}>
114
+ <Text
115
+ ellipsis={{ tooltip: job.content }}
116
+ style={{
117
+ WebkitBoxOrient: 'vertical',
118
+ WebkitLineClamp: 2,
119
+ color: '#666',
120
+ display: '-webkit-box',
121
+ fontSize: '12px',
122
+ overflow: 'hidden',
123
+ }}
124
+ >
125
+ {job.content}
126
+ </Text>
127
+
128
+ <Flexbox gap={8}>
129
+ <Flexbox align="center" gap={6} horizontal>
130
+ <Icon icon={Clock} size={12} />
131
+ <Text style={{ fontSize: '11px' }}>{t(intervalText as any)}</Text>
132
+ </Flexbox>
133
+
134
+ {job.remainingExecutions !== null && (
135
+ <Flexbox align="center" gap={6} horizontal>
136
+ <Text style={{ fontSize: '11px' }}>
137
+ {t('agentCronJobs.remainingExecutions', { count: job.remainingExecutions })}
138
+ </Text>
139
+ </Flexbox>
140
+ )}
141
+
142
+ {job.lastExecutedAt && (
143
+ <Flexbox align="center" gap={6} horizontal>
144
+ <Icon icon={Calendar} size={12} />
145
+ <Text style={{ fontSize: '11px' }}>
146
+ {dayjs(job.lastExecutedAt).format('MM/DD HH:mm')}
147
+ </Text>
148
+ </Flexbox>
149
+ )}
150
+ </Flexbox>
151
+ </Flexbox>
152
+ </Card>
153
+ </Col>
154
+ );
155
+ })}
156
+ </Row>
157
+ );
158
+ });
159
+
160
+ export default CronJobCards;
@@ -0,0 +1,202 @@
1
+ 'use client';
2
+
3
+ import { Checkbox, Form, Input, InputNumber, Select, TimePicker } from 'antd';
4
+ import dayjs from 'dayjs';
5
+ import { memo, useEffect } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
9
+
10
+ // Form data interface - excludes server-managed fields
11
+ interface CronJobFormData {
12
+ content: string;
13
+ cronPattern: string;
14
+ enabled?: boolean;
15
+ executionConditions?: any;
16
+ maxExecutions?: number | null;
17
+ name?: string | null;
18
+ }
19
+
20
+ const { TextArea } = Input;
21
+
22
+ interface CronJobFormProps {
23
+ editingJob?: AgentCronJob;
24
+ formRef?: any;
25
+ onCancel?: () => void;
26
+ onSubmit: (data: CronJobFormData) => void;
27
+ }
28
+
29
+ const CRON_PATTERNS = [
30
+ { label: 'agentCronJobs.interval.30min', value: '0 */30 * * *' },
31
+ { label: 'agentCronJobs.interval.1hour', value: '0 0 * * *' },
32
+ { label: 'agentCronJobs.interval.6hours', value: '0 */6 * * *' },
33
+ { label: 'agentCronJobs.interval.12hours', value: '0 */12 * * *' },
34
+ { label: 'agentCronJobs.interval.daily', value: '0 0 0 * *' },
35
+ { label: 'agentCronJobs.interval.weekly', value: '0 0 0 * 0' },
36
+ ];
37
+
38
+ const WEEKDAY_OPTIONS = [
39
+ { label: 'Monday', value: 1 },
40
+ { label: 'Tuesday', value: 2 },
41
+ { label: 'Wednesday', value: 3 },
42
+ { label: 'Thursday', value: 4 },
43
+ { label: 'Friday', value: 5 },
44
+ { label: 'Saturday', value: 6 },
45
+ { label: 'Sunday', value: 0 },
46
+ ];
47
+
48
+ const CronJobForm = memo<CronJobFormProps>(({ editingJob, formRef, onSubmit }) => {
49
+ const { t } = useTranslation('setting');
50
+ const [form] = Form.useForm();
51
+
52
+ // Expose form instance via ref
53
+ if (formRef) {
54
+ formRef.current = form;
55
+ }
56
+
57
+ useEffect(() => {
58
+ if (editingJob) {
59
+ const conditions = editingJob.executionConditions;
60
+ form.setFieldsValue({
61
+ content: editingJob.content,
62
+ cronPattern: editingJob.cronPattern,
63
+ maxExecutions: editingJob.maxExecutions,
64
+ maxExecutionsPerDay: conditions?.maxExecutionsPerDay,
65
+ name: editingJob.name,
66
+ timeRange: conditions?.timeRange
67
+ ? [dayjs(conditions.timeRange.start, 'HH:mm'), dayjs(conditions.timeRange.end, 'HH:mm')]
68
+ : undefined,
69
+ weekdays: conditions?.weekdays || [],
70
+ });
71
+ } else {
72
+ form.resetFields();
73
+ }
74
+ }, [editingJob, form]);
75
+
76
+ const handleSubmit = async (values: any) => {
77
+ const executionConditions: any = {};
78
+
79
+ if (values.timeRange && values.timeRange.length === 2) {
80
+ executionConditions.timeRange = {
81
+ end: values.timeRange[1].format('HH:mm'),
82
+ start: values.timeRange[0].format('HH:mm'),
83
+ };
84
+ }
85
+
86
+ if (values.weekdays && values.weekdays.length > 0) {
87
+ executionConditions.weekdays = values.weekdays;
88
+ }
89
+
90
+ if (values.maxExecutionsPerDay) {
91
+ executionConditions.maxExecutionsPerDay = values.maxExecutionsPerDay;
92
+ }
93
+
94
+ const data: CronJobFormData = {
95
+ content: values.content,
96
+ cronPattern: values.cronPattern,
97
+ enabled: true,
98
+ executionConditions: Object.keys(executionConditions).length > 0 ? executionConditions : null,
99
+ maxExecutions: values.maxExecutions || null,
100
+ name: values.name,
101
+ };
102
+
103
+ onSubmit(data);
104
+ };
105
+
106
+ const validateTimeRange = (_: any, value: any) => {
107
+ if (!value || value.length !== 2) {
108
+ return Promise.resolve();
109
+ }
110
+
111
+ const [start, end] = value;
112
+ if (start.isAfter(end)) {
113
+ return Promise.reject(new Error(t('agentCronJobs.form.validation.invalidTimeRange')));
114
+ }
115
+
116
+ return Promise.resolve();
117
+ };
118
+
119
+ return (
120
+ <Form
121
+ form={form}
122
+ initialValues={{
123
+ cronPattern: '0 */30 * * *', // Default to 30 minutes
124
+ weekdays: [],
125
+ }}
126
+ layout="vertical"
127
+ onFinish={handleSubmit}
128
+ >
129
+ <Form.Item
130
+ label={t('agentCronJobs.name')}
131
+ name="name"
132
+ rules={[{ message: t('agentCronJobs.form.validation.nameRequired'), required: true }]}
133
+ >
134
+ <Input placeholder={t('agentCronJobs.form.name.placeholder')} />
135
+ </Form.Item>
136
+
137
+ <Form.Item
138
+ label={t('agentCronJobs.content')}
139
+ name="content"
140
+ rules={[{ message: t('agentCronJobs.form.validation.contentRequired'), required: true }]}
141
+ >
142
+ <TextArea
143
+ maxLength={1000}
144
+ placeholder={t('agentCronJobs.form.content.placeholder')}
145
+ rows={3}
146
+ showCount
147
+ />
148
+ </Form.Item>
149
+
150
+ <Form.Item
151
+ label={t('agentCronJobs.schedule')}
152
+ name="cronPattern"
153
+ rules={[{ required: true }]}
154
+ >
155
+ <Select>
156
+ {CRON_PATTERNS.map((pattern) => (
157
+ <Select.Option key={pattern.value} value={pattern.value}>
158
+ {t(pattern.label as any)}
159
+ </Select.Option>
160
+ ))}
161
+ </Select>
162
+ </Form.Item>
163
+
164
+ <Form.Item label={t('agentCronJobs.maxExecutions')} name="maxExecutions">
165
+ <InputNumber
166
+ min={1}
167
+ placeholder={t('agentCronJobs.form.maxExecutions.placeholder')}
168
+ style={{ width: '100%' }}
169
+ />
170
+ </Form.Item>
171
+
172
+ <Form.Item
173
+ label={t('agentCronJobs.timeRange')}
174
+ name="timeRange"
175
+ rules={[{ validator: validateTimeRange }]}
176
+ >
177
+ <TimePicker.RangePicker
178
+ format="HH:mm"
179
+ placeholder={[
180
+ t('agentCronJobs.form.timeRange.start'),
181
+ t('agentCronJobs.form.timeRange.end'),
182
+ ]}
183
+ style={{ width: '100%' }}
184
+ />
185
+ </Form.Item>
186
+
187
+ <Form.Item label={t('agentCronJobs.weekdays')} name="weekdays">
188
+ <Checkbox.Group options={WEEKDAY_OPTIONS} />
189
+ </Form.Item>
190
+
191
+ <Form.Item label="Max Executions Per Day" name="maxExecutionsPerDay">
192
+ <InputNumber
193
+ min={1}
194
+ placeholder="Leave empty for no daily limit"
195
+ style={{ width: '100%' }}
196
+ />
197
+ </Form.Item>
198
+ </Form>
199
+ );
200
+ });
201
+
202
+ export default CronJobForm;
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, Flexbox, Icon } from '@lobehub/ui';
4
+ import { Badge, List, Popconfirm, Switch, Typography } from 'antd';
5
+ import dayjs from 'dayjs';
6
+ import { Calendar, Clock, Edit, Trash2 } from 'lucide-react';
7
+ import { memo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
+
12
+ import { useAgentCronJobs } from './hooks/useAgentCronJobs';
13
+
14
+ const { Text, Paragraph } = Typography;
15
+
16
+ interface CronJobListProps {
17
+ cronJobs: AgentCronJob[];
18
+ loading?: boolean;
19
+ onDelete: (jobId: string) => void;
20
+ onEdit: (jobId: string) => void;
21
+ }
22
+
23
+ const getIntervalText = (cronPattern: string) => {
24
+ const intervalMap: Record<string, string> = {
25
+ '0 */12 * * *': 'agentCronJobs.interval.12hours',
26
+ '0 */30 * * *': 'agentCronJobs.interval.30min',
27
+ '0 */6 * * *': 'agentCronJobs.interval.6hours',
28
+ '0 0 * * *': 'agentCronJobs.interval.1hour',
29
+ '0 0 0 * *': 'agentCronJobs.interval.daily',
30
+ '0 0 0 * 0': 'agentCronJobs.interval.weekly',
31
+ };
32
+
33
+ return intervalMap[cronPattern] || cronPattern;
34
+ };
35
+
36
+ const getStatusInfo = (job: AgentCronJob) => {
37
+ if (!job.enabled) {
38
+ return { status: 'default' as const, text: 'agentCronJobs.status.disabled' };
39
+ }
40
+
41
+ if (job.remainingExecutions === 0) {
42
+ return { status: 'error' as const, text: 'agentCronJobs.status.depleted' };
43
+ }
44
+
45
+ return { status: 'success' as const, text: 'agentCronJobs.status.enabled' };
46
+ };
47
+
48
+ const CronJobList = memo<CronJobListProps>(({ cronJobs, loading, onEdit, onDelete }) => {
49
+ const { t } = useTranslation('setting');
50
+ const { updateCronJob } = useAgentCronJobs();
51
+
52
+ const handleToggleEnabled = async (job: AgentCronJob) => {
53
+ await updateCronJob(job.id, { enabled: !job.enabled });
54
+ };
55
+
56
+ return (
57
+ <List
58
+ dataSource={cronJobs}
59
+ loading={loading}
60
+ renderItem={(job) => {
61
+ const statusInfo = getStatusInfo(job);
62
+ const intervalText = getIntervalText(job.cronPattern);
63
+
64
+ return (
65
+ <List.Item
66
+ actions={[
67
+ <ActionIcon
68
+ icon={Edit}
69
+ key="edit"
70
+ onClick={() => onEdit(job.id)}
71
+ size="small"
72
+ title={t('agentCronJobs.editJob')}
73
+ />,
74
+ <Popconfirm
75
+ key="delete"
76
+ onConfirm={() => onDelete(job.id)}
77
+ title={t('agentCronJobs.confirmDelete')}
78
+ >
79
+ <ActionIcon icon={Trash2} size="small" title={t('agentCronJobs.deleteJob')} />
80
+ </Popconfirm>,
81
+ ]}
82
+ >
83
+ <List.Item.Meta
84
+ avatar={
85
+ <Switch
86
+ checked={job.enabled || false}
87
+ onChange={() => handleToggleEnabled(job)}
88
+ size="small"
89
+ />
90
+ }
91
+ description={
92
+ <Flexbox gap={4}>
93
+ <Paragraph
94
+ ellipsis={{ rows: 2, tooltip: job.content }}
95
+ style={{ color: '#666', fontSize: '12px', margin: 0 }}
96
+ >
97
+ {job.content}
98
+ </Paragraph>
99
+
100
+ <Flexbox gap={8} horizontal style={{ marginTop: 4 }}>
101
+ <Flexbox align="center" gap={4} horizontal>
102
+ <Icon icon={Clock} size={12} />
103
+ <Text style={{ fontSize: '11px' }}>{t(intervalText as any)}</Text>
104
+ </Flexbox>
105
+
106
+ {job.remainingExecutions !== null && (
107
+ <Text style={{ fontSize: '11px' }}>
108
+ {t('agentCronJobs.remainingExecutions', { count: job.remainingExecutions })}
109
+ </Text>
110
+ )}
111
+
112
+ {job.lastExecutedAt && (
113
+ <Flexbox align="center" gap={4} horizontal>
114
+ <Icon icon={Calendar} size={12} />
115
+ <Text style={{ fontSize: '11px' }}>
116
+ {dayjs(job.lastExecutedAt).format('MM/DD HH:mm')}
117
+ </Text>
118
+ </Flexbox>
119
+ )}
120
+ </Flexbox>
121
+ </Flexbox>
122
+ }
123
+ title={
124
+ <Flexbox align="center" gap={8} horizontal>
125
+ <span>{job.name}</span>
126
+ <Badge status={statusInfo.status} text={t(statusInfo.text as any)} />
127
+ </Flexbox>
128
+ }
129
+ />
130
+ </List.Item>
131
+ );
132
+ }}
133
+ />
134
+ );
135
+ });
136
+
137
+ export default CronJobList;
@@ -0,0 +1,138 @@
1
+ import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
+ import { message } from 'antd';
3
+ import { useCallback } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import useSWR from 'swr';
6
+
7
+ import type {
8
+ CreateAgentCronJobData,
9
+ UpdateAgentCronJobData,
10
+ } from '@/database/schemas/agentCronJob';
11
+ import { agentCronJobService } from '@/services/agentCronJob';
12
+
13
+ export const useAgentCronJobs = (agentId?: string) => {
14
+ const { t } = useTranslation('setting');
15
+
16
+ // Fetch cron jobs for the agent
17
+ const {
18
+ data: cronJobs,
19
+ error,
20
+ isLoading: loading,
21
+ mutate,
22
+ } = useSWR(
23
+ ENABLE_BUSINESS_FEATURES && agentId ? `/api/agent-cron-jobs/${agentId}` : null,
24
+ ENABLE_BUSINESS_FEATURES && agentId ? () => agentCronJobService.getByAgentId(agentId) : null,
25
+ {
26
+ onError: (error) => {
27
+ console.error('Failed to fetch cron jobs:', error);
28
+ message.error('Failed to load scheduled tasks');
29
+ },
30
+ },
31
+ );
32
+
33
+ // Create a new cron job
34
+ const createCronJob = useCallback(
35
+ async (data: Omit<CreateAgentCronJobData, 'userId'>) => {
36
+ if (!agentId) return;
37
+
38
+ try {
39
+ const result = await agentCronJobService.create({
40
+ ...data,
41
+ agentId,
42
+ });
43
+
44
+ if (result.success) {
45
+ message.success(t('agentCronJobs.createSuccess'));
46
+ await mutate();
47
+ return result.data;
48
+ }
49
+ } catch (error) {
50
+ console.error('Failed to create cron job:', error);
51
+ message.error('Failed to create scheduled task');
52
+ throw error;
53
+ }
54
+ },
55
+ [agentId, mutate, t],
56
+ );
57
+
58
+ // Update a cron job
59
+ const updateCronJob = useCallback(
60
+ async (id: string, data: UpdateAgentCronJobData) => {
61
+ try {
62
+ const result = await agentCronJobService.update(id, data);
63
+
64
+ if (result.success) {
65
+ message.success(t('agentCronJobs.updateSuccess'));
66
+ await mutate();
67
+ return result.data;
68
+ }
69
+ } catch (error) {
70
+ console.error('Failed to update cron job:', error);
71
+ message.error('Failed to update scheduled task');
72
+ throw error;
73
+ }
74
+ },
75
+ [mutate, t],
76
+ );
77
+
78
+ // Delete a cron job
79
+ const deleteCronJob = useCallback(
80
+ async (id: string) => {
81
+ try {
82
+ const result = await agentCronJobService.delete(id);
83
+
84
+ if (result.success) {
85
+ message.success(t('agentCronJobs.deleteSuccess'));
86
+ await mutate();
87
+ }
88
+ } catch (error) {
89
+ console.error('Failed to delete cron job:', error);
90
+ message.error('Failed to delete scheduled task');
91
+ throw error;
92
+ }
93
+ },
94
+ [mutate, t],
95
+ );
96
+
97
+ // Get execution statistics
98
+ const getStats = useCallback(async () => {
99
+ try {
100
+ return await agentCronJobService.getStats();
101
+ } catch (error) {
102
+ console.error('Failed to get cron job stats:', error);
103
+ throw error;
104
+ }
105
+ }, []);
106
+
107
+ // Reset execution counts
108
+ const resetExecutions = useCallback(
109
+ async (id: string, newMaxExecutions?: number) => {
110
+ try {
111
+ const result = await agentCronJobService.resetExecutions(id, newMaxExecutions);
112
+
113
+ if (result.success) {
114
+ message.success('Execution counts reset successfully');
115
+ await mutate();
116
+ return result.data;
117
+ }
118
+ } catch (error) {
119
+ console.error('Failed to reset executions:', error);
120
+ message.error('Failed to reset execution counts');
121
+ throw error;
122
+ }
123
+ },
124
+ [mutate],
125
+ );
126
+
127
+ return {
128
+ createCronJob,
129
+ cronJobs: cronJobs?.data || [],
130
+ deleteCronJob,
131
+ error,
132
+ getStats,
133
+ loading,
134
+ refetch: mutate,
135
+ resetExecutions,
136
+ updateCronJob,
137
+ };
138
+ };