@lobehub/lobehub 2.0.0-next.250 → 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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
- package/packages/database/src/models/topic.ts +71 -4
- package/packages/database/src/schemas/agentCronJob.ts +1 -2
- package/packages/memory-user-memory/src/extractors/context.ts +1 -4
- package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
- package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
- package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
- package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
- package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
- package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
- package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
- package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
- package/packages/memory-user-memory/src/types.ts +10 -8
- package/packages/types/src/discover/mcp.ts +1 -1
- package/packages/types/src/topic/topic.ts +9 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
- package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
- package/src/app/[variants]/(main)/community/(detail)/assistant/features/Sidebar/ActionButton/AddAgent.tsx +6 -0
- package/src/app/[variants]/(main)/community/(list)/assistant/features/List/Item.tsx +12 -3
- package/src/app/[variants]/(main)/community/(list)/mcp/features/List/Item.tsx +14 -4
- package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
- package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +1 -1
- package/src/hooks/useFetchCronTopics.ts +29 -0
- package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
- package/src/hooks/useFetchTopics.ts +4 -1
- package/src/locales/default/setting.ts +44 -1
- package/src/server/routers/lambda/agentCronJob.ts +367 -0
- package/src/server/routers/lambda/image/index.test.ts +2 -2
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/market/index.ts +45 -4
- package/src/server/routers/lambda/topic.ts +15 -3
- package/src/server/services/aiAgent/index.ts +18 -1
- package/src/server/services/discover/index.ts +29 -3
- package/src/server/services/memory/userMemory/extract.ts +14 -6
- package/src/services/agentCronJob.ts +95 -0
- package/src/services/discover.ts +38 -1
- package/src/services/topic/index.ts +1 -0
- package/src/store/chat/slices/topic/action.ts +53 -2
- package/src/store/chat/slices/topic/initialState.ts +1 -0
- package/src/store/chat/slices/topic/selectors.ts +14 -6
- package/src/store/tool/slices/mcpStore/action.test.ts +38 -0
- package/src/store/tool/slices/mcpStore/action.ts +18 -0
- 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;
|
package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts
ADDED
|
@@ -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
|
+
};
|