@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/changelog/v1.json +14 -0
  3. package/locales/en-US/setting.json +11 -0
  4. package/locales/zh-CN/setting.json +11 -0
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/BatchCreateAgents/index.tsx +2 -2
  7. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/UpdateGroup/index.tsx +56 -56
  8. package/packages/builtin-tool-group-agent-builder/src/client/Render/BatchCreateAgents.tsx +3 -2
  9. package/packages/builtin-tool-group-agent-builder/src/executor.ts +2 -1
  10. package/packages/types/src/agentCronJob/index.ts +19 -23
  11. package/packages/types/src/serverConfig.ts +1 -0
  12. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/Actions.tsx +31 -0
  13. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +10 -6
  14. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/index.tsx +7 -11
  15. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/useDropdownMenu.tsx +102 -0
  16. package/src/app/[variants]/(main)/agent/cron/[cronId]/CronConfig.ts +179 -0
  17. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +111 -0
  18. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobHeader.tsx +45 -0
  19. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobSaveButton.tsx +31 -0
  20. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +213 -0
  21. package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +186 -344
  22. package/src/app/[variants]/(main)/agent/features/Conversation/index.tsx +8 -2
  23. package/src/app/[variants]/(main)/agent/features/Portal/_layout/Mobile.tsx +1 -0
  24. package/src/app/[variants]/(main)/agent/features/Portal/features/Portal.tsx +4 -2
  25. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/index.tsx +42 -97
  26. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +4 -20
  27. package/src/app/[variants]/(main)/community/features/UserAvatar/index.tsx +15 -5
  28. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +1 -6
  29. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +1 -0
  30. package/src/app/[variants]/(main)/group/features/Portal/features/Portal.tsx +4 -2
  31. package/src/hooks/useYamlArguments.ts +11 -8
  32. package/src/server/globalConfig/index.ts +1 -0
  33. package/src/services/chatGroup/index.ts +1 -4
  34. package/src/store/serverConfig/selectors.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.277](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.276...v2.0.0-next.277)
6
+
7
+ <sup>Released on **2026-01-13**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Update the community user layout action button, update the cron job visiual way.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Update the community user layout action button, closes [#11472](https://github.com/lobehub/lobe-chat/issues/11472) ([2dd6d42](https://github.com/lobehub/lobe-chat/commit/2dd6d42))
21
+ - **misc**: Update the cron job visiual way, closes [#11466](https://github.com/lobehub/lobe-chat/issues/11466) ([63d81de](https://github.com/lobehub/lobe-chat/commit/63d81de))
22
+
23
+ </details>
24
+
25
+ <div align="right">
26
+
27
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
28
+
29
+ </div>
30
+
31
+ ## [Version 2.0.0-next.276](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.275...v2.0.0-next.276)
32
+
33
+ <sup>Released on **2026-01-13**</sup>
34
+
35
+ <br/>
36
+
37
+ <details>
38
+ <summary><kbd>Improvements and Fixes</kbd></summary>
39
+
40
+ </details>
41
+
42
+ <div align="right">
43
+
44
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
45
+
46
+ </div>
47
+
5
48
  ## [Version 2.0.0-next.275](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.274...v2.0.0-next.275)
6
49
 
7
50
  <sup>Released on **2026-01-13**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Update the community user layout action button, update the cron job visiual way."
6
+ ]
7
+ },
8
+ "date": "2026-01-13",
9
+ "version": "2.0.0-next.277"
10
+ },
11
+ {
12
+ "children": {},
13
+ "date": "2026-01-13",
14
+ "version": "2.0.0-next.276"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "features": [
@@ -3,10 +3,16 @@
3
3
  "about.title": "About",
4
4
  "advancedSettings": "Advanced Settings",
5
5
  "agentCronJobs.addJob": "Add Scheduled Task",
6
+ "agentCronJobs.clearTopics": "Clear Execution Results",
7
+ "agentCronJobs.clearTopicsFailed": "Failed to clear execution results",
8
+ "agentCronJobs.confirmClearTopics": "Are you sure you want to clear {{count}} execution result(s)? This action cannot be undone.",
6
9
  "agentCronJobs.confirmDelete": "Are you sure you want to delete this scheduled task?",
10
+ "agentCronJobs.confirmDeleteCronJob": "Are you sure you want to delete this scheduled task and all its execution results? This action cannot be undone.",
7
11
  "agentCronJobs.content": "Task Content",
8
12
  "agentCronJobs.create": "Create",
9
13
  "agentCronJobs.createSuccess": "Scheduled task created successfully",
14
+ "agentCronJobs.deleteCronJob": "Delete Scheduled Task",
15
+ "agentCronJobs.deleteFailed": "Failed to delete scheduled task",
10
16
  "agentCronJobs.deleteJob": "Delete Task",
11
17
  "agentCronJobs.deleteSuccess": "Scheduled task deleted successfully",
12
18
  "agentCronJobs.description": "Automate your agent with scheduled executions",
@@ -25,6 +31,7 @@
25
31
  "agentCronJobs.form.validation.nameRequired": "Task name is required",
26
32
  "agentCronJobs.interval.12hours": "Every 12 hours",
27
33
  "agentCronJobs.interval.1hour": "Every hour",
34
+ "agentCronJobs.interval.2hours": "Every 2 hours",
28
35
  "agentCronJobs.interval.30min": "Every 30 minutes",
29
36
  "agentCronJobs.interval.6hours": "Every 6 hours",
30
37
  "agentCronJobs.interval.daily": "Daily",
@@ -36,7 +43,11 @@
36
43
  "agentCronJobs.noExecutionResults": "No execution results",
37
44
  "agentCronJobs.remainingExecutions": "Remaining: {{count}}",
38
45
  "agentCronJobs.save": "Save",
46
+ "agentCronJobs.saveAsNew": "Save as New Scheduled Task",
39
47
  "agentCronJobs.schedule": "Schedule",
48
+ "agentCronJobs.scheduleType.daily": "Daily",
49
+ "agentCronJobs.scheduleType.hourly": "Hourly",
50
+ "agentCronJobs.scheduleType.weekly": "Weekly",
40
51
  "agentCronJobs.status.depleted": "Depleted",
41
52
  "agentCronJobs.status.disabled": "Disabled",
42
53
  "agentCronJobs.status.enabled": "Enabled",
@@ -3,10 +3,16 @@
3
3
  "about.title": "关于",
4
4
  "advancedSettings": "进阶配置",
5
5
  "agentCronJobs.addJob": "添加定时任务",
6
+ "agentCronJobs.clearTopics": "清空执行结果",
7
+ "agentCronJobs.clearTopicsFailed": "清空执行结果失败",
8
+ "agentCronJobs.confirmClearTopics": "确定要清空 {{count}} 条执行结果吗?此操作不可撤销。",
6
9
  "agentCronJobs.confirmDelete": "确定要删除此定时任务吗?",
10
+ "agentCronJobs.confirmDeleteCronJob": "确定要删除此定时任务及其所有执行结果吗?此操作不可撤销。",
7
11
  "agentCronJobs.content": "任务内容",
8
12
  "agentCronJobs.create": "创建",
9
13
  "agentCronJobs.createSuccess": "定时任务创建成功",
14
+ "agentCronJobs.deleteCronJob": "删除定时任务",
15
+ "agentCronJobs.deleteFailed": "删除定时任务失败",
10
16
  "agentCronJobs.deleteJob": "删除任务",
11
17
  "agentCronJobs.deleteSuccess": "定时任务删除成功",
12
18
  "agentCronJobs.description": "通过定时执行自动化您的智能体",
@@ -25,6 +31,7 @@
25
31
  "agentCronJobs.form.validation.nameRequired": "任务名称不能为空",
26
32
  "agentCronJobs.interval.12hours": "每12小时",
27
33
  "agentCronJobs.interval.1hour": "每小时",
34
+ "agentCronJobs.interval.2hours": "每2小时",
28
35
  "agentCronJobs.interval.30min": "每30分钟",
29
36
  "agentCronJobs.interval.6hours": "每6小时",
30
37
  "agentCronJobs.interval.daily": "每日",
@@ -36,7 +43,11 @@
36
43
  "agentCronJobs.noExecutionResults": "无执行结果",
37
44
  "agentCronJobs.remainingExecutions": "剩余:{{count}}",
38
45
  "agentCronJobs.save": "保存",
46
+ "agentCronJobs.saveAsNew": "保存为新定时任务",
39
47
  "agentCronJobs.schedule": "计划",
48
+ "agentCronJobs.scheduleType.daily": "每日",
49
+ "agentCronJobs.scheduleType.hourly": "每小时",
50
+ "agentCronJobs.scheduleType.weekly": "每周",
40
51
  "agentCronJobs.status.depleted": "已耗尽",
41
52
  "agentCronJobs.status.disabled": "已禁用",
42
53
  "agentCronJobs.status.enabled": "已启用",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.275",
3
+ "version": "2.0.0-next.277",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -18,8 +18,8 @@ const styles = createStaticStyles(({ css, cssVar: cv }) => ({
18
18
  align-items: center;
19
19
  `,
20
20
  count: css`
21
- color: ${cv.colorTextSecondary};
22
21
  font-size: 12px;
22
+ color: ${cv.colorTextSecondary};
23
23
  `,
24
24
  root: css`
25
25
  overflow: hidden;
@@ -83,8 +83,8 @@ export const BatchCreateAgentsInspector = memo<
83
83
  <div className={styles.avatarGroup}>
84
84
  {displayInfo.displayAgents.map((agent, index) => (
85
85
  <Avatar
86
- key={index}
87
86
  avatar={agent.avatar}
87
+ key={index}
88
88
  shape={'square'}
89
89
  size={20}
90
90
  title={agent.title}
@@ -17,70 +17,70 @@ const styles = createStaticStyles(({ css }) => ({
17
17
  `,
18
18
  }));
19
19
 
20
- export const UpdateGroupInspector = memo<BuiltinInspectorProps<UpdateGroupParams, UpdateGroupState>>(
21
- ({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
22
- const { t } = useTranslation('plugin');
20
+ export const UpdateGroupInspector = memo<
21
+ BuiltinInspectorProps<UpdateGroupParams, UpdateGroupState>
22
+ >(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
23
+ const { t } = useTranslation('plugin');
23
24
 
24
- const config = args?.config || partialArgs?.config;
25
- const meta = args?.meta || partialArgs?.meta;
25
+ const config = args?.config || partialArgs?.config;
26
+ const meta = args?.meta || partialArgs?.meta;
26
27
 
27
- // Build display text from updated fields
28
- const displayText = useMemo(() => {
29
- const fields: string[] = [];
30
- // Config fields
31
- if (config?.openingMessage !== undefined) {
32
- fields.push(t('builtins.lobe-group-agent-builder.inspector.openingMessage'));
33
- }
34
- if (config?.openingQuestions !== undefined) {
35
- fields.push(t('builtins.lobe-group-agent-builder.inspector.openingQuestions'));
36
- }
37
- // Meta fields
38
- if (meta?.title !== undefined) {
39
- fields.push(t('builtins.lobe-group-agent-builder.inspector.title'));
40
- }
41
- if (meta?.description !== undefined) {
42
- fields.push(t('builtins.lobe-group-agent-builder.inspector.description'));
43
- }
44
- if (meta?.avatar !== undefined) {
45
- fields.push(t('builtins.lobe-group-agent-builder.inspector.avatar'));
46
- }
47
- if (meta?.backgroundColor !== undefined) {
48
- fields.push(t('builtins.lobe-group-agent-builder.inspector.backgroundColor'));
49
- }
50
- return fields.length > 0 ? fields.join(', ') : '';
51
- }, [config, meta, t]);
52
-
53
- // Initial streaming state
54
- if (isArgumentsStreaming && !displayText) {
55
- return (
56
- <div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
57
- <span>{t('builtins.lobe-group-agent-builder.apiName.updateGroup')}</span>
58
- </div>
59
- );
28
+ // Build display text from updated fields
29
+ const displayText = useMemo(() => {
30
+ const fields: string[] = [];
31
+ // Config fields
32
+ if (config?.openingMessage !== undefined) {
33
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.openingMessage'));
60
34
  }
35
+ if (config?.openingQuestions !== undefined) {
36
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.openingQuestions'));
37
+ }
38
+ // Meta fields
39
+ if (meta?.title !== undefined) {
40
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.title'));
41
+ }
42
+ if (meta?.description !== undefined) {
43
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.description'));
44
+ }
45
+ if (meta?.avatar !== undefined) {
46
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.avatar'));
47
+ }
48
+ if (meta?.backgroundColor !== undefined) {
49
+ fields.push(t('builtins.lobe-group-agent-builder.inspector.backgroundColor'));
50
+ }
51
+ return fields.length > 0 ? fields.join(', ') : '';
52
+ }, [config, meta, t]);
61
53
 
62
- const isSuccess = pluginState?.success;
63
-
54
+ // Initial streaming state
55
+ if (isArgumentsStreaming && !displayText) {
64
56
  return (
65
- <div
66
- className={cx(
67
- inspectorTextStyles.root,
68
- (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
69
- )}
70
- >
57
+ <div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
71
58
  <span>{t('builtins.lobe-group-agent-builder.apiName.updateGroup')}</span>
72
- {displayText && (
73
- <>
74
- : <span className={highlightTextStyles.primary}>{displayText}</span>
75
- </>
76
- )}
77
- {!isLoading && isSuccess && (
78
- <Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
79
- )}
80
59
  </div>
81
60
  );
82
- },
83
- );
61
+ }
62
+
63
+ const isSuccess = pluginState?.success;
64
+
65
+ return (
66
+ <div
67
+ className={cx(
68
+ inspectorTextStyles.root,
69
+ (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
70
+ )}
71
+ >
72
+ <span>{t('builtins.lobe-group-agent-builder.apiName.updateGroup')}</span>
73
+ {displayText && (
74
+ <>
75
+ : <span className={highlightTextStyles.primary}>{displayText}</span>
76
+ </>
77
+ )}
78
+ {!isLoading && isSuccess && (
79
+ <Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
80
+ )}
81
+ </div>
82
+ );
83
+ });
84
84
 
85
85
  UpdateGroupInspector.displayName = 'UpdateGroupInspector';
86
86
 
@@ -11,9 +11,10 @@ import type { BatchCreateAgentsParams, BatchCreateAgentsState } from '../../type
11
11
 
12
12
  const styles = createStaticStyles(({ css, cssVar }) => ({
13
13
  container: css`
14
- padding: 4px 16px;
15
- background: ${cssVar.colorFillQuaternary};
14
+ padding-block: 4px;
15
+ padding-inline: 16px;
16
16
  border-radius: 8px;
17
+ background: ${cssVar.colorFillQuaternary};
17
18
  `,
18
19
  description: css`
19
20
  overflow: hidden;
@@ -238,7 +238,8 @@ class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApi
238
238
 
239
239
  if (!agentId) {
240
240
  return {
241
- content: 'No agent found. Please provide an agentId or ensure supervisor context is available.',
241
+ content:
242
+ 'No agent found. Please provide an agentId or ensure supervisor context is available.',
242
243
  error: { message: 'No agent found', type: 'NoAgentContext' },
243
244
  success: false,
244
245
  };
@@ -21,25 +21,6 @@ export const cronPatternSchema = z
21
21
  // Minimum 30 minutes validation (using standard cron format)
22
22
  export const minimumIntervalSchema = z.string().refine((pattern) => {
23
23
  // Standard cron format: minute hour day month weekday
24
- const allowedPatterns = [
25
- '*/30 * * * *', // Every 30 minutes
26
- '0 * * * *', // Every hour
27
- '0 */2 * * *', // Every 2 hours
28
- '0 */3 * * *', // Every 3 hours
29
- '0 */4 * * *', // Every 4 hours
30
- '0 */6 * * *', // Every 6 hours
31
- '0 */8 * * *', // Every 8 hours
32
- '0 */12 * * *', // Every 12 hours
33
- '0 0 * * *', // Daily at midnight
34
- '0 0 * * 0', // Weekly on Sunday
35
- '0 0 1 * *', // Monthly on 1st
36
- ];
37
-
38
- // Check if it matches allowed patterns
39
- if (allowedPatterns.includes(pattern)) {
40
- return true;
41
- }
42
-
43
24
  // Parse pattern to validate minimum 30-minute interval
44
25
  const parts = pattern.split(' ');
45
26
  if (parts.length !== 5) {
@@ -48,24 +29,39 @@ export const minimumIntervalSchema = z.string().refine((pattern) => {
48
29
 
49
30
  const [minute, hour] = parts;
50
31
 
32
+ // Validate minute is 0 or 30 (we only allow 30-minute intervals)
33
+ const isValidMinute = minute === '0' || minute === '30' || minute === '*/30';
34
+
35
+ if (!isValidMinute) {
36
+ return false;
37
+ }
38
+
51
39
  // Allow minute intervals >= 30 (e.g., */30, */45, */60)
52
40
  if (minute.startsWith('*/')) {
53
41
  const interval = parseInt(minute.slice(2));
54
42
  if (!isNaN(interval) && interval >= 30) {
55
43
  return true;
56
44
  }
45
+ return false;
57
46
  }
58
47
 
59
- // Allow hourly patterns: 0 */N * * * where N >= 1
60
- if (minute === '0' && hour.startsWith('*/')) {
48
+ // Allow hourly patterns: {0|30} */N * * * where N >= 1
49
+ if ((minute === '0' || minute === '30') && hour.startsWith('*/')) {
61
50
  const interval = parseInt(hour.slice(2));
62
51
  if (!isNaN(interval) && interval >= 1) {
63
52
  return true;
64
53
  }
54
+ return false;
55
+ }
56
+
57
+ // Allow hourly patterns: {0|30} * * * * (every hour at :00 or :30)
58
+ if ((minute === '0' || minute === '30') && hour === '*') {
59
+ return true;
65
60
  }
66
61
 
67
- // Allow specific hour patterns: 0 N * * * (runs once per day)
68
- if (minute === '0' && /^\d+$/.test(hour)) {
62
+ // Allow specific hour patterns: {0|30} N * * * (runs once per day)
63
+ // or {0|30} N * * {weekdays} (runs on specific weekdays)
64
+ if ((minute === '0' || minute === '30') && /^\d+$/.test(hour)) {
69
65
  const h = parseInt(hour);
70
66
  if (!isNaN(h) && h >= 0 && h <= 23) {
71
67
  return true;
@@ -49,6 +49,7 @@ export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerMod
49
49
  export interface GlobalServerConfig {
50
50
  aiProvider: ServerLanguageModel;
51
51
  defaultAgent?: PartialDeep<UserDefaultAgent>;
52
+ enableBusinessFeatures?: boolean;
52
53
  enableEmailVerification?: boolean;
53
54
  enableKlavis?: boolean;
54
55
  enableLobehubSkill?: boolean;
@@ -0,0 +1,31 @@
1
+ import { ActionIcon, Dropdown } from '@lobehub/ui';
2
+ import { MoreHorizontal } from 'lucide-react';
3
+ import { memo } from 'react';
4
+
5
+ import { useCronJobDropdownMenu } from './useDropdownMenu';
6
+
7
+ interface ActionsProps {
8
+ cronJobId: string;
9
+ topics: Array<{ id: string }>;
10
+ }
11
+
12
+ const Actions = memo<ActionsProps>(({ cronJobId, topics }) => {
13
+ const dropdownMenu = useCronJobDropdownMenu(cronJobId, topics);
14
+
15
+ return (
16
+ <Dropdown
17
+ arrow={false}
18
+ menu={{
19
+ items: dropdownMenu,
20
+ onClick: ({ domEvent }) => {
21
+ domEvent.stopPropagation();
22
+ },
23
+ }}
24
+ trigger={['click']}
25
+ >
26
+ <ActionIcon icon={MoreHorizontal} size={'small'} />
27
+ </Dropdown>
28
+ );
29
+ });
30
+
31
+ export default Actions;
@@ -9,6 +9,7 @@ import { useParams } from 'react-router-dom';
9
9
  import { useRouter } from '@/app/[variants]/(main)/hooks/useRouter';
10
10
  import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
11
 
12
+ import Actions from './Actions';
12
13
  import CronTopicItem from './CronTopicItem';
13
14
 
14
15
  interface CronTopicGroupProps {
@@ -43,12 +44,15 @@ const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }
43
44
  return (
44
45
  <AccordionItem
45
46
  action={
46
- <ActionIcon
47
- icon={Settings2Icon}
48
- onClick={handleOpenCronJob}
49
- size="small"
50
- title={t('agentCronJobs.editJob')}
51
- />
47
+ <Flexbox align="center" gap={4} horizontal>
48
+ <ActionIcon
49
+ icon={Settings2Icon}
50
+ onClick={handleOpenCronJob}
51
+ size="small"
52
+ title={t('agentCronJobs.editJob')}
53
+ />
54
+ <Actions cronJobId={cronJobId} topics={topics} />
55
+ </Flexbox>
52
56
  }
53
57
  itemKey={cronJobId}
54
58
  paddingBlock={4}
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
3
  import { Accordion, AccordionItem, ActionIcon, Flexbox, Text } from '@lobehub/ui';
5
4
  import { Plus } from 'lucide-react';
6
5
  import { memo, useCallback } from 'react';
@@ -12,6 +11,7 @@ import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
12
11
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
13
12
  import { useQueryRoute } from '@/hooks/useQueryRoute';
14
13
  import { useAgentStore } from '@/store/agent';
14
+ import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
15
15
 
16
16
  import CronTopicGroup from './CronTopicGroup';
17
17
 
@@ -22,24 +22,20 @@ interface CronTopicListProps {
22
22
  const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
23
23
  const { t } = useTranslation('setting');
24
24
  const router = useQueryRoute();
25
- const [agentId, createAgentCronJob, useFetchCronTopicsWithJobInfo] = useAgentStore((s) => [
25
+ const [agentId, useFetchCronTopicsWithJobInfo] = useAgentStore((s) => [
26
26
  s.activeAgentId,
27
- s.createAgentCronJob,
28
27
  s.useFetchCronTopicsWithJobInfo,
29
28
  ]);
30
29
  const { data: cronTopicsGroupsWithJobInfo = [], isLoading } =
31
30
  useFetchCronTopicsWithJobInfo(agentId);
31
+ const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
32
32
 
33
- const handleCreateCronJob = useCallback(async () => {
33
+ const handleCreateCronJob = useCallback(() => {
34
34
  if (!agentId) return;
35
+ router.push(urlJoin('/agent', agentId, 'cron', 'new'));
36
+ }, [agentId, router]);
35
37
 
36
- const cronJobId = await createAgentCronJob();
37
- if (cronJobId) {
38
- router.push(urlJoin('/agent', agentId, 'cron', cronJobId));
39
- }
40
- }, [agentId, createAgentCronJob, router]);
41
-
42
- if (!ENABLE_BUSINESS_FEATURES) return null;
38
+ if (!enableBusinessFeatures) return null;
43
39
 
44
40
  const addAction = (
45
41
  <ActionIcon
@@ -0,0 +1,102 @@
1
+ import { Icon, type MenuProps } from '@lobehub/ui';
2
+ import { App } from 'antd';
3
+ import { Trash } from 'lucide-react';
4
+ import { useCallback, useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { agentCronJobService } from '@/services/agentCronJob';
8
+ import { topicService } from '@/services/topic';
9
+ import { useAgentStore } from '@/store/agent';
10
+
11
+ export const useCronJobDropdownMenu = (
12
+ cronJobId: string,
13
+ topics: Array<{ id: string }>,
14
+ ): MenuProps['items'] => {
15
+ const { t } = useTranslation(['setting', 'common']);
16
+ const { modal } = App.useApp();
17
+
18
+ const refreshCronTopics = useAgentStore((s) => s.internal_refreshCronTopics);
19
+
20
+ const handleDeleteCronJob = useCallback(async () => {
21
+ try {
22
+ // Delete all topics associated with this cron job
23
+ if (topics.length > 0) {
24
+ const topicIds = topics.map((t) => t.id);
25
+ await topicService.batchRemoveTopics(topicIds);
26
+ }
27
+
28
+ // Delete the cron job
29
+ await agentCronJobService.delete(cronJobId);
30
+
31
+ // Refresh the cron topics list
32
+ await refreshCronTopics();
33
+ } catch (error) {
34
+ console.error('Failed to delete cron job:', error);
35
+ modal.error({
36
+ content: t('agentCronJobs.deleteFailed' as any),
37
+ title: t('error' as any, { ns: 'common' }),
38
+ });
39
+ }
40
+ }, [cronJobId, topics, refreshCronTopics, modal, t]);
41
+
42
+ const handleClearTopics = useCallback(async () => {
43
+ if (topics.length === 0) return;
44
+
45
+ try {
46
+ const topicIds = topics.map((t) => t.id);
47
+ await topicService.batchRemoveTopics(topicIds);
48
+
49
+ // Refresh the cron topics list
50
+ await refreshCronTopics();
51
+ } catch (error) {
52
+ console.error('Failed to clear topics:', error);
53
+ modal.error({
54
+ content: t('agentCronJobs.clearTopicsFailed' as any),
55
+ title: t('error' as any, { ns: 'common' }),
56
+ });
57
+ }
58
+ }, [topics, refreshCronTopics, modal, t]);
59
+
60
+ return useMemo(
61
+ () =>
62
+ [
63
+ {
64
+ icon: <Icon icon={Trash} />,
65
+ key: 'clearTopics',
66
+ label: t('agentCronJobs.clearTopics' as any),
67
+ onClick: () => {
68
+ modal.confirm({
69
+ cancelText: t('cancel', { ns: 'common' }),
70
+ centered: true,
71
+ content: t('agentCronJobs.confirmClearTopics' as any, { count: topics.length }),
72
+ okButtonProps: { danger: true },
73
+ okText: t('ok', { ns: 'common' }),
74
+ onOk: handleClearTopics,
75
+ title: t('agentCronJobs.clearTopics' as any),
76
+ });
77
+ },
78
+ },
79
+ {
80
+ type: 'divider' as const,
81
+ },
82
+ {
83
+ danger: true,
84
+ icon: <Icon icon={Trash} />,
85
+ key: 'deleteCronJob',
86
+ label: t('agentCronJobs.deleteCronJob' as any),
87
+ onClick: () => {
88
+ modal.confirm({
89
+ cancelText: t('cancel', { ns: 'common' }),
90
+ centered: true,
91
+ content: t('agentCronJobs.confirmDeleteCronJob' as any),
92
+ okButtonProps: { danger: true },
93
+ okText: t('ok', { ns: 'common' }),
94
+ onOk: handleDeleteCronJob,
95
+ title: t('agentCronJobs.deleteCronJob' as any),
96
+ });
97
+ },
98
+ },
99
+ ].filter(Boolean) as MenuProps['items'],
100
+ [topics.length, handleClearTopics, handleDeleteCronJob, t, modal],
101
+ );
102
+ };