feat: refactor cron job UI and use runtime enableBusinessFeatures flag (#11975)

- Replace compile-time ENABLE_BUSINESS_FEATURES constant with runtime
  serverConfigSelectors.enableBusinessFeatures for cron module
- Redesign CronJobScheduleConfig with FormGroup pattern matching Settings UI
- Update CronJobHeader with simplified layout (28px title, Switch only)
- Convert all cron feature components to use createStaticStyles with cssVar
- Add i18n keys for cron job form labels

LOBE-4540
This commit is contained in:
Innei
2026-01-30 13:38:34 +08:00
committed by GitHub
parent c5a1791e32
commit 104a19a8a4
12 changed files with 319 additions and 224 deletions

View File

@@ -34,11 +34,20 @@
"agentCronJobs.empty.description": "Create your first scheduled task to automate your agent",
"agentCronJobs.empty.title": "No scheduled tasks yet",
"agentCronJobs.enable": "Enable",
"agentCronJobs.form.at": "at",
"agentCronJobs.form.content.placeholder": "Enter the prompt or instruction for the agent",
"agentCronJobs.form.every": "Every",
"agentCronJobs.form.frequency": "Frequency",
"agentCronJobs.form.hours": "hour(s)",
"agentCronJobs.form.maxExecutions": "Stop after",
"agentCronJobs.form.maxExecutions.placeholder": "Leave empty for unlimited",
"agentCronJobs.form.name.placeholder": "Enter task name",
"agentCronJobs.form.time": "Time",
"agentCronJobs.form.timeRange.end": "End Time",
"agentCronJobs.form.timeRange.start": "Start Time",
"agentCronJobs.form.times": "times",
"agentCronJobs.form.timezone": "Timezone",
"agentCronJobs.form.unlimited": "Run continuously",
"agentCronJobs.form.validation.contentRequired": "Task content is required",
"agentCronJobs.form.validation.invalidTimeRange": "Start time must be before end time",
"agentCronJobs.form.validation.nameRequired": "Task name is required",
@@ -83,6 +92,13 @@
"agentCronJobs.weekday.tuesday": "Tuesday",
"agentCronJobs.weekday.wednesday": "Wednesday",
"agentCronJobs.weekdays": "Weekdays",
"agentCronJobs.weekdays.fri": "Fri",
"agentCronJobs.weekdays.mon": "Mon",
"agentCronJobs.weekdays.sat": "Sat",
"agentCronJobs.weekdays.sun": "Sun",
"agentCronJobs.weekdays.thu": "Thu",
"agentCronJobs.weekdays.tue": "Tue",
"agentCronJobs.weekdays.wed": "Wed",
"agentInfoDescription.basic.avatar": "Avatar",
"agentInfoDescription.basic.description": "Description",
"agentInfoDescription.basic.name": "Name",

View File

@@ -34,11 +34,20 @@
"agentCronJobs.empty.description": "创建您的第一个定时任务以实现智能体自动化",
"agentCronJobs.empty.title": "暂无定时任务",
"agentCronJobs.enable": "启用",
"agentCronJobs.form.at": "于",
"agentCronJobs.form.content.placeholder": "输入智能体的提示词或指令",
"agentCronJobs.form.every": "每",
"agentCronJobs.form.frequency": "执行频率",
"agentCronJobs.form.hours": "小时",
"agentCronJobs.form.maxExecutions": "执行",
"agentCronJobs.form.maxExecutions.placeholder": "留空表示无限次",
"agentCronJobs.form.name.placeholder": "输入任务名称",
"agentCronJobs.form.time": "时间",
"agentCronJobs.form.timeRange.end": "结束时间",
"agentCronJobs.form.timeRange.start": "开始时间",
"agentCronJobs.form.times": "次后停止",
"agentCronJobs.form.timezone": "时区",
"agentCronJobs.form.unlimited": "持续执行",
"agentCronJobs.form.validation.contentRequired": "任务内容不能为空",
"agentCronJobs.form.validation.invalidTimeRange": "开始时间必须早于结束时间",
"agentCronJobs.form.validation.nameRequired": "任务名称不能为空",
@@ -83,6 +92,13 @@
"agentCronJobs.weekday.tuesday": "星期二",
"agentCronJobs.weekday.wednesday": "星期三",
"agentCronJobs.weekdays": "工作日",
"agentCronJobs.weekdays.fri": "周五",
"agentCronJobs.weekdays.mon": "周一",
"agentCronJobs.weekdays.sat": "周六",
"agentCronJobs.weekdays.sun": "周日",
"agentCronJobs.weekdays.thu": "周四",
"agentCronJobs.weekdays.tue": "周二",
"agentCronJobs.weekdays.wed": "周三",
"agentInfoDescription.basic.avatar": "头像",
"agentInfoDescription.basic.description": "描述",
"agentInfoDescription.basic.name": "名称",

View File

@@ -26,9 +26,11 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
s.activeAgentId,
s.useFetchCronTopicsWithJobInfo,
]);
const { data: cronTopicsGroupsWithJobInfo = [], isLoading } =
useFetchCronTopicsWithJobInfo(agentId);
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
const { data: cronTopicsGroupsWithJobInfo = [], isLoading } = useFetchCronTopicsWithJobInfo(
agentId,
enableBusinessFeatures,
);
const handleCreateCronJob = useCallback(() => {
if (!agentId) return;

View File

@@ -1,3 +1,5 @@
'use client';
import {
ReactCodePlugin,
ReactCodemirrorPlugin,
@@ -8,12 +10,19 @@ import {
ReactTablePlugin,
} from '@lobehub/editor';
import { Editor, useEditor } from '@lobehub/editor/react';
import { Flexbox, Icon, Text } from '@lobehub/ui';
import { Card } from 'antd';
import { Clock } from 'lucide-react';
import { FormGroup } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { memo, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
const styles = createStaticStyles(({ css }) => ({
editorWrapper: css`
min-height: 200px;
padding-block: 8px;
padding-inline: 0;
`,
}));
interface CronJobContentEditorProps {
enableRichRender: boolean;
initialValue: string;
@@ -26,12 +35,10 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
const editor = useEditor();
const currentValueRef = useRef(initialValue);
// Update currentValueRef when initialValue changes
useEffect(() => {
currentValueRef.current = initialValue;
}, [initialValue]);
// Initialize editor content when editor is ready
useEffect(() => {
if (!editor) return;
try {
@@ -48,7 +55,6 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
}
}, [editor, enableRichRender, initialValue]);
// Handle content changes
const handleContentChange = useCallback(
(e: any) => {
const nextContent = enableRichRender
@@ -57,7 +63,6 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
const finalContent = nextContent || '';
// Only call onChange if content actually changed
if (finalContent !== currentValueRef.current) {
currentValueRef.current = finalContent;
onChange(finalContent);
@@ -67,43 +72,33 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
);
return (
<Flexbox gap={12}>
<Flexbox align="center" gap={6} horizontal>
<Icon icon={Clock} size={16} />
<Text style={{ fontWeight: 600 }}>{t('agentCronJobs.content')}</Text>
</Flexbox>
<Card
size="small"
style={{ borderRadius: 12, overflow: 'hidden' }}
styles={{ body: { padding: 0 } }}
>
<Flexbox padding={16} style={{ minHeight: 220 }}>
<Editor
content={''}
editor={editor}
lineEmptyPlaceholder={t('agentCronJobs.form.content.placeholder')}
onTextChange={handleContentChange}
placeholder={t('agentCronJobs.form.content.placeholder')}
plugins={
enableRichRender
? [
ReactListPlugin,
ReactCodePlugin,
ReactCodemirrorPlugin,
ReactHRPlugin,
ReactLinkPlugin,
ReactTablePlugin,
ReactMathPlugin,
]
: undefined
}
style={{ paddingBottom: 48 }}
type={'text'}
variant={'chat'}
/>
</Flexbox>
</Card>
</Flexbox>
<FormGroup title={t('agentCronJobs.content')} variant="filled">
<div className={styles.editorWrapper}>
<Editor
content={''}
editor={editor}
lineEmptyPlaceholder={t('agentCronJobs.form.content.placeholder')}
onTextChange={handleContentChange}
placeholder={t('agentCronJobs.form.content.placeholder')}
plugins={
enableRichRender
? [
ReactListPlugin,
ReactCodePlugin,
ReactCodemirrorPlugin,
ReactHRPlugin,
ReactLinkPlugin,
ReactTablePlugin,
ReactMathPlugin,
]
: undefined
}
style={{ paddingBottom: 48 }}
type={'text'}
variant={'chat'}
/>
</div>
</FormGroup>
);
},
);

View File

@@ -1,8 +1,19 @@
import { Flexbox, Input } from '@lobehub/ui';
import { Switch } from 'antd';
'use client';
import { Flexbox, Input, LobeSwitch as Switch } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const styles = createStaticStyles(({ css }) => ({
titleInput: css`
flex: 1;
font-size: 28px;
font-weight: 500;
line-height: 1.4;
`,
}));
interface CronJobHeaderProps {
enabled?: boolean;
isNewJob?: boolean;
@@ -17,30 +28,26 @@ const CronJobHeader = memo<CronJobHeaderProps>(
const { t } = useTranslation(['setting', 'common']);
return (
<Flexbox gap={16}>
{/* Title Input */}
<Flexbox
align="center"
gap={16}
horizontal
justify="space-between"
style={{ marginBottom: 8 }}
>
<Input
className={styles.titleInput}
onChange={(e) => onNameChange(e.target.value)}
placeholder={t('agentCronJobs.form.name.placeholder')}
style={{
fontSize: 28,
fontWeight: 600,
padding: 0,
}}
value={name}
variant={'borderless'}
variant="borderless"
/>
{/* Controls Row */}
{!isNewJob && (
<Flexbox align="center" gap={12} horizontal>
{/* Enable/Disable Switch */}
<Switch
checked={enabled ?? false}
loading={isTogglingEnabled}
onChange={onToggleEnabled}
/>
</Flexbox>
<Switch
checked={enabled ?? false}
loading={isTogglingEnabled}
onChange={onToggleEnabled}
/>
)}
</Flexbox>
);

View File

@@ -1,3 +1,5 @@
'use client';
import { Button, Flexbox } from '@lobehub/ui';
import { Save } from 'lucide-react';
import { memo } from 'react';
@@ -13,13 +15,13 @@ const CronJobSaveButton = memo<CronJobSaveButtonProps>(({ disabled, loading, onS
const { t } = useTranslation('setting');
return (
<Flexbox paddingBlock={16}>
<Flexbox paddingBlock={8}>
<Button
disabled={disabled}
icon={Save}
loading={loading}
onClick={onSave}
style={{ width: 200 }}
style={{ maxWidth: 200, width: '100%' }}
type="primary"
>
{t('agentCronJobs.saveAsNew')}

View File

@@ -1,17 +1,62 @@
import { Flexbox, Tag, Text } from '@lobehub/ui';
import { Card, InputNumber, Select, TimePicker } from 'antd';
'use client';
import { Checkbox, Flexbox, FormGroup, LobeSelect as Select, Text } from '@lobehub/ui';
import { Divider, InputNumber, TimePicker } from 'antd';
import { createStaticStyles, cx } from 'antd-style';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { memo, useMemo } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import {
SCHEDULE_TYPE_OPTIONS,
type ScheduleType,
TIMEZONE_OPTIONS,
WEEKDAY_LABELS,
WEEKDAY_OPTIONS,
} from '../CronConfig';
import { SCHEDULE_TYPE_OPTIONS, type ScheduleType, TIMEZONE_OPTIONS } from '../CronConfig';
const styles = createStaticStyles(({ css, cssVar }) => ({
label: css`
flex-shrink: 0;
width: 120px;
`,
row: css`
min-height: 48px;
padding-block: 12px;
padding-inline: 0;
`,
weekdayButton: css`
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 32px;
border: 1px solid ${cssVar.colorBorder};
border-radius: 6px;
font-size: 12px;
font-weight: 500;
color: ${cssVar.colorTextSecondary};
background: transparent;
transition: all 0.15s ease;
&:hover {
border-color: ${cssVar.colorPrimary};
color: ${cssVar.colorPrimary};
}
`,
weekdayButtonActive: css`
border-color: ${cssVar.colorPrimary};
color: ${cssVar.colorTextLightSolid};
background: ${cssVar.colorPrimary};
&:hover {
border-color: ${cssVar.colorPrimaryHover};
color: ${cssVar.colorTextLightSolid};
background: ${cssVar.colorPrimaryHover};
}
`,
}));
interface CronJobScheduleConfigProps {
hourlyInterval?: number;
@@ -30,6 +75,16 @@ interface CronJobScheduleConfigProps {
weekdays: number[];
}
const WEEKDAYS = [
{ key: 1, label: 'agentCronJobs.weekdays.mon' },
{ key: 2, label: 'agentCronJobs.weekdays.tue' },
{ key: 3, label: 'agentCronJobs.weekdays.wed' },
{ key: 4, label: 'agentCronJobs.weekdays.thu' },
{ key: 5, label: 'agentCronJobs.weekdays.fri' },
{ key: 6, label: 'agentCronJobs.weekdays.sat' },
{ key: 0, label: 'agentCronJobs.weekdays.sun' },
];
const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
({
hourlyInterval,
@@ -42,172 +97,155 @@ const CronJobScheduleConfig = memo<CronJobScheduleConfigProps>(
}) => {
const { t } = useTranslation('setting');
// Compute summary tags
const summaryTags = useMemo(() => {
const result: Array<{ key: string; label: string }> = [];
const toggleWeekday = (day: number) => {
const newWeekdays = weekdays.includes(day)
? weekdays.filter((d) => d !== day)
: [...weekdays, day];
onScheduleChange({ weekdays: newWeekdays });
};
// Schedule type
const scheduleTypeLabel = SCHEDULE_TYPE_OPTIONS.find(
(opt) => opt.value === scheduleType,
)?.label;
if (scheduleTypeLabel) {
result.push({
key: 'scheduleType',
label: t(scheduleTypeLabel as any),
});
}
// Trigger time
if (scheduleType === 'hourly') {
const minute = triggerTime.minute();
result.push({
key: 'interval',
label: `Every ${hourlyInterval || 1} hour(s) at :${minute.toString().padStart(2, '0')}`,
});
} else {
result.push({
key: 'triggerTime',
label: triggerTime.format('HH:mm'),
});
}
// Timezone
result.push({
key: 'timezone',
label: timezone,
});
// Weekdays for weekly schedule
if (scheduleType === 'weekly' && weekdays.length > 0) {
result.push({
key: 'weekdays',
label: weekdays.map((day) => t(WEEKDAY_LABELS[day] as any)).join(', '),
});
}
return result;
}, [scheduleType, triggerTime, timezone, weekdays, hourlyInterval, t]);
const isUnlimited = maxExecutions === null || maxExecutions === undefined;
return (
<Card size="small" style={{ borderRadius: 12 }} styles={{ body: { padding: 12 } }}>
<Flexbox gap={12}>
{/* Summary Tags */}
{summaryTags.length > 0 && (
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
{summaryTags.map((tag) => (
<Tag key={tag.key} variant={'filled'}>
{tag.label}
</Tag>
))}
</Flexbox>
)}
{/* Schedule Configuration - All in one row */}
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
<Tag variant={'borderless'}>{t('agentCronJobs.schedule')}</Tag>
<Select
onChange={(value: ScheduleType) =>
onScheduleChange({
scheduleType: value,
weekdays: value === 'weekly' ? [0, 1, 2, 3, 4, 5, 6] : [],
})
}
options={SCHEDULE_TYPE_OPTIONS.map((opt) => ({
label: t(opt.label as any),
value: opt.value,
}))}
size="small"
style={{ minWidth: 120 }}
value={scheduleType}
/>
<FormGroup title={t('agentCronJobs.schedule')} variant="filled">
{/* Frequency Row */}
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.form.frequency')}</Text>
<Select
onChange={(value: ScheduleType) =>
onScheduleChange({
scheduleType: value,
weekdays: value === 'weekly' ? [1, 2, 3, 4, 5] : [],
})
}
options={SCHEDULE_TYPE_OPTIONS.map((opt) => ({
label: t(opt.label as any),
value: opt.value,
}))}
style={{ width: 140 }}
value={scheduleType}
variant="outlined"
/>
</Flexbox>
{/* Weekdays - show only for weekly */}
{scheduleType === 'weekly' && (
<Select
maxTagCount="responsive"
mode="multiple"
onChange={(values: number[]) => onScheduleChange({ weekdays: values })}
options={WEEKDAY_OPTIONS.map((opt) => ({
label: t(opt.label as any),
value: opt.value,
}))}
placeholder="Select days"
size="small"
style={{ minWidth: 150 }}
value={weekdays}
/>
)}
<Divider style={{ margin: 0 }} />
{/* Trigger Time - show for daily and weekly */}
{scheduleType !== 'hourly' && (
{/* Time Row (for daily/weekly) */}
{scheduleType !== 'hourly' && (
<>
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.form.time')}</Text>
<TimePicker
format="HH:mm"
minuteStep={30}
minuteStep={15}
onChange={(value) => {
if (value) onScheduleChange({ triggerTime: value });
}}
size="small"
style={{ minWidth: 120 }}
value={triggerTime ?? dayjs().hour(0).minute(0)}
style={{ width: 120 }}
value={triggerTime ?? dayjs().hour(9).minute(0)}
/>
)}
</Flexbox>
<Divider style={{ margin: 0 }} />
</>
)}
{/* Hourly Interval - show only for hourly */}
{scheduleType === 'hourly' && (
<>
<Tag variant={'borderless'}>Every</Tag>
{/* Hourly Interval Row */}
{scheduleType === 'hourly' && (
<>
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.form.every')}</Text>
<Flexbox align="center" gap={8} horizontal>
<InputNumber
max={24}
min={1}
onChange={(value: number | null) =>
onScheduleChange({ hourlyInterval: value ?? 1 })
}
size="small"
style={{ width: 80 }}
onChange={(value) => onScheduleChange({ hourlyInterval: value ?? 1 })}
style={{ width: 70 }}
value={hourlyInterval ?? 1}
/>
<Text type="secondary">hour(s) at</Text>
<Text type="secondary">{t('agentCronJobs.form.hours')}</Text>
<Text type="secondary">{t('agentCronJobs.form.at')}</Text>
<Select
onChange={(value: number) =>
onScheduleChange({ triggerTime: dayjs().hour(0).minute(value) })
}
options={[
{ label: ':00', value: 0 },
{ label: ':15', value: 15 },
{ label: ':30', value: 30 },
{ label: ':45', value: 45 },
]}
size="small"
style={{ width: 80 }}
style={{ width: '80px' }}
value={triggerTime?.minute() ?? 0}
variant="outlined"
/>
</>
)}
</Flexbox>
</Flexbox>
<Divider style={{ margin: 0 }} />
</>
)}
{/* Timezone */}
<Select
onChange={(value: string) => onScheduleChange({ timezone: value })}
options={TIMEZONE_OPTIONS}
showSearch
size="small"
style={{ maxWidth: 300, minWidth: 200 }}
value={timezone}
/>
</Flexbox>
{/* Weekday Selector (only for weekly) */}
{scheduleType === 'weekly' && (
<>
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.weekdays')}</Text>
<Flexbox gap={6} horizontal>
{WEEKDAYS.map(({ key, label }) => (
<div
className={cx(
styles.weekdayButton,
weekdays.includes(key) && styles.weekdayButtonActive,
)}
key={key}
onClick={() => toggleWeekday(key)}
>
{t(label as any)}
</div>
))}
</Flexbox>
</Flexbox>
<Divider style={{ margin: 0 }} />
</>
)}
{/* Max Executions */}
<Flexbox align="center" gap={8} horizontal style={{ flexWrap: 'wrap' }}>
<Tag variant={'borderless'}>{t('agentCronJobs.maxExecutions')}</Tag>
{/* Timezone Row */}
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.form.timezone')}</Text>
<Select
onChange={(value: string) => onScheduleChange({ timezone: value })}
options={TIMEZONE_OPTIONS}
popupMatchSelectWidth={false}
showSearch
style={{ minWidth: '200px', width: 'fit-content' }}
value={timezone}
variant="outlined"
/>
</Flexbox>
<Divider style={{ margin: 0 }} />
{/* Execution Limit Row */}
<Flexbox align="center" className={styles.row} gap={24} horizontal>
<Text className={styles.label}>{t('agentCronJobs.maxExecutions')}</Text>
<Flexbox align="center" gap={12} horizontal>
<InputNumber
disabled={isUnlimited}
min={1}
onChange={(value: number | null) =>
onScheduleChange({ maxExecutions: value ?? null })
}
placeholder={t('agentCronJobs.form.maxExecutions.placeholder')}
size="small"
style={{ width: 160 }}
value={maxExecutions ?? null}
onChange={(value) => onScheduleChange({ maxExecutions: value })}
placeholder="100"
style={{ width: 100 }}
value={maxExecutions ?? undefined}
/>
<Text type="secondary">{t('agentCronJobs.form.times')}</Text>
<Checkbox
checked={isUnlimited}
onChange={(checked) => onScheduleChange({ maxExecutions: checked ? null : 100 })}
>
{t('agentCronJobs.form.unlimited')}
</Checkbox>
</Flexbox>
</Flexbox>
</Card>
</FormGroup>
);
},
);

View File

@@ -1,4 +1,3 @@
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
import { message } from 'antd';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,7 +9,7 @@ import type {
} from '@/database/schemas/agentCronJob';
import { agentCronJobService } from '@/services/agentCronJob';
export const useAgentCronJobs = (agentId?: string) => {
export const useAgentCronJobs = (agentId?: string, enabled: boolean = true) => {
const { t } = useTranslation('setting');
// Fetch cron jobs for the agent
@@ -20,8 +19,8 @@ export const useAgentCronJobs = (agentId?: string) => {
isLoading: loading,
mutate,
} = useSWR(
ENABLE_BUSINESS_FEATURES && agentId ? `/api/agent-cron-jobs/${agentId}` : null,
ENABLE_BUSINESS_FEATURES && agentId ? () => agentCronJobService.getByAgentId(agentId) : null,
enabled && agentId ? `/api/agent-cron-jobs/${agentId}` : null,
enabled && agentId ? () => agentCronJobService.getByAgentId(agentId) : null,
{
onError: (error) => {
console.error('Failed to fetch cron jobs:', error);

View File

@@ -1,6 +1,5 @@
'use client';
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
import { Flexbox } from '@lobehub/ui';
import { Typography } from 'antd';
import { Clock } from 'lucide-react';
@@ -10,6 +9,7 @@ import urlJoin from 'url-join';
import { useQueryRoute } from '@/hooks/useQueryRoute';
import { useAgentStore } from '@/store/agent';
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
import CronJobCards from './CronJobCards';
import { useAgentCronJobs } from './hooks/useAgentCronJobs';
@@ -20,8 +20,9 @@ const AgentCronJobs = memo(() => {
const { t } = useTranslation('setting');
const agentId = useAgentStore((s) => s.activeAgentId);
const router = useQueryRoute();
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
const { cronJobs, loading, deleteCronJob } = useAgentCronJobs(agentId);
const { cronJobs, loading, deleteCronJob } = useAgentCronJobs(agentId, enableBusinessFeatures);
// Edit: Navigate to cron job detail page
const handleEdit = useCallback(
@@ -40,7 +41,7 @@ const AgentCronJobs = memo(() => {
[deleteCronJob],
);
if (!ENABLE_BUSINESS_FEATURES) return null;
if (!enableBusinessFeatures) return null;
if (!agentId) {
return null;

View File

@@ -1,6 +1,5 @@
'use client';
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
import { Button, Flexbox } from '@lobehub/ui';
import { Divider } from 'antd';
import { useTheme } from 'antd-style';
@@ -15,6 +14,7 @@ import { useQueryRoute } from '@/hooks/useQueryRoute';
import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { useChatStore } from '@/store/chat';
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
import AgentCronJobs from '../AgentCronJobs';
import AgentSettings from '../AgentSettings';
@@ -31,6 +31,7 @@ const ProfileEditor = memo(() => {
const agentId = useAgentStore((s) => s.activeAgentId);
const switchTopic = useChatStore((s) => s.switchTopic);
const router = useQueryRoute();
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
const handleCreateCronJob = useCallback(() => {
if (!agentId) return;
@@ -95,7 +96,7 @@ const ProfileEditor = memo(() => {
{t('startConversation')}
</Button>
<AgentPublishButton />
{ENABLE_BUSINESS_FEATURES && (
{enableBusinessFeatures && (
<Button icon={Clock} onClick={handleCreateCronJob}>
{t('agentCronJobs.addJob')}
</Button>
@@ -106,7 +107,7 @@ const ProfileEditor = memo(() => {
{/* Main Content: Prompt Editor */}
<EditorCanvas />
{/* Agent Cron Jobs Display (only show if jobs exist) */}
{ENABLE_BUSINESS_FEATURES && <AgentCronJobs />}
{enableBusinessFeatures && <AgentCronJobs />}
{/* Advanced Settings Modal */}
<AgentSettings />
</>

View File

@@ -37,11 +37,20 @@ export default {
'agentCronJobs.empty.description': 'Create your first scheduled task to automate your agent',
'agentCronJobs.empty.title': 'No scheduled tasks yet',
'agentCronJobs.enable': 'Enable',
'agentCronJobs.form.at': 'at',
'agentCronJobs.form.content.placeholder': 'Enter the prompt or instruction for the agent',
'agentCronJobs.form.every': 'Every',
'agentCronJobs.form.frequency': 'Frequency',
'agentCronJobs.form.hours': 'hour(s)',
'agentCronJobs.form.maxExecutions': 'Stop after',
'agentCronJobs.form.maxExecutions.placeholder': 'Leave empty for unlimited',
'agentCronJobs.form.name.placeholder': 'Enter task name',
'agentCronJobs.form.time': 'Time',
'agentCronJobs.form.timeRange.end': 'End Time',
'agentCronJobs.form.timeRange.start': 'Start Time',
'agentCronJobs.form.times': 'times',
'agentCronJobs.form.timezone': 'Timezone',
'agentCronJobs.form.unlimited': 'Run continuously',
'agentCronJobs.form.validation.contentRequired': 'Task content is required',
'agentCronJobs.form.validation.invalidTimeRange': 'Start time must be before end time',
'agentCronJobs.form.validation.nameRequired': 'Task name is required',
@@ -86,6 +95,13 @@ export default {
'agentCronJobs.weekday.tuesday': 'Tuesday',
'agentCronJobs.weekday.wednesday': 'Wednesday',
'agentCronJobs.weekdays': 'Weekdays',
'agentCronJobs.weekdays.fri': 'Fri',
'agentCronJobs.weekdays.mon': 'Mon',
'agentCronJobs.weekdays.sat': 'Sat',
'agentCronJobs.weekdays.sun': 'Sun',
'agentCronJobs.weekdays.thu': 'Thu',
'agentCronJobs.weekdays.tue': 'Tue',
'agentCronJobs.weekdays.wed': 'Wed',
'agentInfoDescription.basic.avatar': 'Avatar',
'agentInfoDescription.basic.description': 'Description',
'agentInfoDescription.basic.name': 'Name',

View File

@@ -1,4 +1,3 @@
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
import type { SWRResponse } from 'swr';
import { type StateCreator } from 'zustand/vanilla';
@@ -33,7 +32,10 @@ export interface CronTopicGroupWithJobInfo {
export interface CronSliceAction {
createAgentCronJob: () => Promise<string | null>;
internal_refreshCronTopics: () => Promise<void>;
useFetchCronTopicsWithJobInfo: (agentId?: string) => SWRResponse<CronTopicGroupWithJobInfo[]>;
useFetchCronTopicsWithJobInfo: (
agentId?: string,
enabled?: boolean,
) => SWRResponse<CronTopicGroupWithJobInfo[]>;
}
export const createCronSlice: StateCreator<
@@ -69,9 +71,9 @@ export const createCronSlice: StateCreator<
await mutate([FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY, get().activeAgentId]);
},
useFetchCronTopicsWithJobInfo: (agentId) =>
useFetchCronTopicsWithJobInfo: (agentId, enabled = true) =>
useClientDataSWR<CronTopicGroupWithJobInfo[]>(
ENABLE_BUSINESS_FEATURES && agentId ? [FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY, agentId] : null,
enabled && agentId ? [FETCH_CRON_TOPICS_WITH_JOB_INFO_KEY, agentId] : null,
async ([, id]: [string, string]) => {
const [cronJobsResult, cronTopicsGroups] = await Promise.all([
lambdaClient.agentCronJob.findByAgent.query({ agentId: id }),