@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
@@ -1,5 +1,5 @@
1
1
  import { Flexbox, TooltipGroup } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import React, { memo } from 'react';
3
3
 
4
4
  import DragUploadZone, { useUploadFiles } from '@/components/DragUploadZone';
5
5
  import { useAgentStore } from '@/store/agent';
@@ -10,6 +10,12 @@ import { systemStatusSelectors } from '@/store/global/selectors';
10
10
  import ConversationArea from './ConversationArea';
11
11
  import ChatHeader from './Header';
12
12
 
13
+ const wrapperStyle: React.CSSProperties = {
14
+ height: '100%',
15
+ minWidth: 300,
16
+ width: '100%',
17
+ };
18
+
13
19
  const ChatConversation = memo(() => {
14
20
  const showHeader = useGlobalStore(systemStatusSelectors.showChatHeader);
15
21
 
@@ -19,7 +25,7 @@ const ChatConversation = memo(() => {
19
25
  const { handleUploadFiles } = useUploadFiles({ model, provider });
20
26
 
21
27
  return (
22
- <DragUploadZone onUploadFiles={handleUploadFiles} style={{ height: '100%', width: '100%' }}>
28
+ <DragUploadZone onUploadFiles={handleUploadFiles} style={wrapperStyle}>
23
29
  <Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
24
30
  {showHeader && <ChatHeader />}
25
31
  <TooltipGroup>
@@ -39,6 +39,7 @@ const Layout = () => {
39
39
  <Modal
40
40
  allowFullscreen
41
41
  className={cx(isPortalThread && styles.container)}
42
+ destroyOnHidden
42
43
  footer={null}
43
44
  height={'95%'}
44
45
  onCancel={() => togglePortal(false)}
@@ -3,7 +3,7 @@
3
3
  import { DraggablePanel, type DraggablePanelProps } from '@lobehub/ui';
4
4
  import { createStaticStyles } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
6
- import { type PropsWithChildren, memo, useState } from 'react';
6
+ import { Activity, type PropsWithChildren, memo, useState } from 'react';
7
7
 
8
8
  import {
9
9
  CHAT_PORTAL_MAX_WIDTH,
@@ -81,7 +81,9 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
81
81
  showHandleWideArea={false}
82
82
  size={{ height: '100%', width: portalWidth }}
83
83
  >
84
- {children}
84
+ <Activity mode={showPortal ? 'visible' : 'hidden'} name="AgentPortal">
85
+ {children}
86
+ </Activity>
85
87
  </DraggablePanel>
86
88
  );
87
89
  });
@@ -2,38 +2,43 @@
2
2
 
3
3
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
4
  import { Flexbox } from '@lobehub/ui';
5
- import { Modal, Typography } from 'antd';
5
+ import { Typography } from 'antd';
6
6
  import { Clock } from 'lucide-react';
7
- import { memo, useRef, useState } from 'react';
7
+ import { memo, useCallback } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
+ import urlJoin from 'url-join';
9
10
 
11
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
10
12
  import { useAgentStore } from '@/store/agent';
11
13
 
12
14
  import CronJobCards from './CronJobCards';
13
- import CronJobForm from './CronJobForm';
14
15
  import { useAgentCronJobs } from './hooks/useAgentCronJobs';
15
16
 
16
17
  const { Title } = Typography;
17
18
 
18
- interface AgentCronJobsProps {
19
- onFormModalChange?: (show: boolean) => void;
20
- showFormModal?: boolean;
21
- }
22
-
23
- const AgentCronJobs = memo<AgentCronJobsProps>(({ showFormModal, onFormModalChange }) => {
19
+ const AgentCronJobs = memo(() => {
24
20
  const { t } = useTranslation('setting');
25
21
  const agentId = useAgentStore((s) => s.activeAgentId);
26
- const [internalShowForm, setInternalShowForm] = useState(false);
27
- const [editingJob, setEditingJob] = useState<string | null>(null);
28
- const [submitting, setSubmitting] = useState(false);
29
- const formRef = useRef<any>(null);
22
+ const router = useQueryRoute();
23
+
24
+ const { cronJobs, loading, deleteCronJob } = useAgentCronJobs(agentId);
30
25
 
31
- // Use external control if provided, otherwise use internal state
32
- const showForm = showFormModal ?? internalShowForm;
33
- const setShowForm = onFormModalChange ?? setInternalShowForm;
26
+ // Edit: Navigate to cron job detail page
27
+ const handleEdit = useCallback(
28
+ (jobId: string) => {
29
+ if (!agentId) return;
30
+ router.push(urlJoin('/agent', agentId, 'cron', jobId));
31
+ },
32
+ [agentId, router],
33
+ );
34
34
 
35
- const { cronJobs, loading, createCronJob, updateCronJob, deleteCronJob } =
36
- useAgentCronJobs(agentId);
35
+ // Delete: Keep the existing delete logic
36
+ const handleDelete = useCallback(
37
+ async (jobId: string) => {
38
+ await deleteCronJob(jobId);
39
+ },
40
+ [deleteCronJob],
41
+ );
37
42
 
38
43
  if (!ENABLE_BUSINESS_FEATURES) return null;
39
44
 
@@ -41,89 +46,29 @@ const AgentCronJobs = memo<AgentCronJobsProps>(({ showFormModal, onFormModalChan
41
46
  return null;
42
47
  }
43
48
 
44
- const handleCreate = async (data: any) => {
45
- setSubmitting(true);
46
- try {
47
- await createCronJob(data);
48
- setShowForm(false);
49
- } finally {
50
- setSubmitting(false);
51
- }
52
- };
53
-
54
- const handleEdit = (jobId: string) => {
55
- setEditingJob(jobId);
56
- setShowForm(true);
57
- };
58
-
59
- const handleUpdate = async (data: any) => {
60
- if (editingJob) {
61
- setSubmitting(true);
62
- try {
63
- await updateCronJob(editingJob, data);
64
- setShowForm(false);
65
- setEditingJob(null);
66
- } finally {
67
- setSubmitting(false);
68
- }
69
- }
70
- };
71
-
72
- const handleCancel = () => {
73
- setShowForm(false);
74
- setEditingJob(null);
75
- formRef.current?.resetFields();
76
- };
77
-
78
- const handleModalOk = () => {
79
- formRef.current?.submit();
80
- };
81
-
82
- const handleDelete = async (jobId: string) => {
83
- await deleteCronJob(jobId);
84
- };
85
-
86
49
  const hasCronJobs = cronJobs && cronJobs.length > 0;
87
50
 
88
- return (
89
- <>
90
- {/* Show cards section only if there are jobs */}
91
- {hasCronJobs && (
92
- <Flexbox gap={12} style={{ marginBottom: 16, marginTop: 16 }}>
93
- <Title level={5} style={{ margin: 0 }}>
94
- <Flexbox align="center" gap={8} horizontal>
95
- <Clock size={16} />
96
- {t('agentCronJobs.title')}
97
- </Flexbox>
98
- </Title>
51
+ // Only show if there are jobs
52
+ if (!hasCronJobs) {
53
+ return null;
54
+ }
99
55
 
100
- <CronJobCards
101
- cronJobs={cronJobs}
102
- loading={loading}
103
- onDelete={handleDelete}
104
- onEdit={handleEdit}
105
- />
56
+ return (
57
+ <Flexbox gap={12} style={{ marginBottom: 16, marginTop: 16 }}>
58
+ <Title level={5} style={{ margin: 0 }}>
59
+ <Flexbox align="center" gap={8} horizontal>
60
+ <Clock size={16} />
61
+ {t('agentCronJobs.title')}
106
62
  </Flexbox>
107
- )}
108
-
109
- {/* Form Modal */}
110
- <Modal
111
- confirmLoading={submitting}
112
- okText={editingJob ? t('agentCronJobs.save' as any) : t('agentCronJobs.create' as any)}
113
- onCancel={handleCancel}
114
- onOk={handleModalOk}
115
- open={showForm}
116
- title={editingJob ? t('agentCronJobs.editJob') : t('agentCronJobs.addJob')}
117
- width={640}
118
- >
119
- <CronJobForm
120
- editingJob={editingJob ? cronJobs?.find((job) => job.id === editingJob) : undefined}
121
- formRef={formRef}
122
- onCancel={handleCancel}
123
- onSubmit={editingJob ? handleUpdate : handleCreate}
124
- />
125
- </Modal>
126
- </>
63
+ </Title>
64
+
65
+ <CronJobCards
66
+ cronJobs={cronJobs}
67
+ loading={loading}
68
+ onDelete={handleDelete}
69
+ onEdit={handleEdit}
70
+ />
71
+ </Flexbox>
127
72
  );
128
73
  });
129
74
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
4
  import { Button, Flexbox } from '@lobehub/ui';
5
- import { Divider, message } from 'antd';
5
+ import { Divider } from 'antd';
6
6
  import isEqual from 'fast-deep-equal';
7
7
  import { Clock, PlayIcon } from 'lucide-react';
8
8
  import React, { memo, useCallback } from 'react';
@@ -11,7 +11,6 @@ import urlJoin from 'url-join';
11
11
 
12
12
  import ModelSelect from '@/features/ModelSelect';
13
13
  import { useQueryRoute } from '@/hooks/useQueryRoute';
14
- import { agentCronJobService } from '@/services/agentCronJob';
15
14
  import { useAgentStore } from '@/store/agent';
16
15
  import { agentSelectors } from '@/store/agent/selectors';
17
16
  import { useChatStore } from '@/store/chat';
@@ -29,25 +28,10 @@ const ProfileEditor = memo(() => {
29
28
  const switchTopic = useChatStore((s) => s.switchTopic);
30
29
  const router = useQueryRoute();
31
30
 
32
- const handleCreateCronJob = useCallback(async () => {
31
+ const handleCreateCronJob = useCallback(() => {
33
32
  if (!agentId) return;
34
- try {
35
- const result = await agentCronJobService.create({
36
- agentId,
37
- content: t('agentCronJobs.form.content.placeholder') || 'This is a cron job',
38
- cronPattern: '*/30 * * * *',
39
- enabled: true,
40
- name: t('agentCronJobs.addJob') || 'Cron Job Task',
41
- });
42
-
43
- if (result.success) {
44
- router.push(urlJoin('/agent', agentId, 'cron', result.data.id));
45
- }
46
- } catch (error) {
47
- console.error('Failed to create cron job:', error);
48
- message.error('Failed to create scheduled task');
49
- }
50
- }, [agentId, router, t]);
33
+ router.push(urlJoin('/agent', agentId, 'cron', 'new'));
34
+ }, [agentId, router]);
51
35
 
52
36
  return (
53
37
  <>
@@ -16,7 +16,14 @@ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
16
16
  */
17
17
  const checkNeedsProfileSetup = (
18
18
  enableMarketTrustedClient: boolean,
19
- userProfile: { avatarUrl: string | null; bannerUrl: string | null; socialLinks: { github?: string; twitter?: string; website?: string } | null } | null | undefined,
19
+ userProfile:
20
+ | {
21
+ avatarUrl: string | null;
22
+ bannerUrl: string | null;
23
+ socialLinks: { github?: string; twitter?: string; website?: string } | null;
24
+ }
25
+ | null
26
+ | undefined,
20
27
  ): boolean => {
21
28
  if (!enableMarketTrustedClient) return false;
22
29
  if (!userProfile) return true;
@@ -33,7 +40,9 @@ const UserAvatar = memo(() => {
33
40
  const [loading, setLoading] = useState(false);
34
41
  const { isAuthenticated, isLoading, getCurrentUserInfo, signIn } = useMarketAuth();
35
42
 
36
- const enableMarketTrustedClient = useServerConfigStore(serverConfigSelectors.enableMarketTrustedClient);
43
+ const enableMarketTrustedClient = useServerConfigStore(
44
+ serverConfigSelectors.enableMarketTrustedClient,
45
+ );
37
46
 
38
47
  const userInfo = getCurrentUserInfo();
39
48
  const username = userInfo?.sub;
@@ -70,8 +79,9 @@ const UserAvatar = memo(() => {
70
79
  return <Skeleton.Avatar active shape={'square'} size={28} style={{ borderRadius: 6 }} />;
71
80
  }
72
81
 
73
- // 未认证,或者是 trustedClient 模式但需要完善资料时,显示登录按钮
74
- if (!isAuthenticated || needsProfileSetup) {
82
+ // 如果启用了 trustedClient,不显示"成为创作者"按钮,直接显示头像
83
+ // 否则,未认证或需要完善资料时,显示登录按钮
84
+ if (!enableMarketTrustedClient && (!isAuthenticated || needsProfileSetup)) {
75
85
  return (
76
86
  <Button
77
87
  icon={UserCircleIcon}
@@ -92,7 +102,7 @@ const UserAvatar = memo(() => {
92
102
 
93
103
  return (
94
104
  <Avatar
95
- avatar={avatarUrl || userProfile?.userName}
105
+ avatar={avatarUrl || userProfile?.userName || username}
96
106
  onClick={handleAvatarClick}
97
107
  shape={'square'}
98
108
  size={28}
@@ -136,12 +136,7 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
136
136
  </Text>
137
137
 
138
138
  {/* Settings Button */}
139
- <Flexbox
140
- align="center"
141
- horizontal
142
- justify="flex-end"
143
- style={{ paddingBlockStart: 0 }}
144
- >
139
+ <Flexbox align="center" horizontal justify="flex-end" style={{ paddingBlockStart: 0 }}>
145
140
  <ActionIcon
146
141
  icon={Settings}
147
142
  onClick={handleSettings}
@@ -39,6 +39,7 @@ const Layout = () => {
39
39
  <Modal
40
40
  allowFullscreen
41
41
  className={cx(isPortalThread && styles.container)}
42
+ destroyOnHidden
42
43
  footer={null}
43
44
  height={'95%'}
44
45
  onCancel={() => togglePortal(false)}
@@ -4,7 +4,7 @@ import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } fro
4
4
  import { Flexbox } from '@lobehub/ui';
5
5
  import { createStaticStyles, useResponsive } from 'antd-style';
6
6
  import isEqual from 'fast-deep-equal';
7
- import { type PropsWithChildren, memo, useState } from 'react';
7
+ import { Activity, type PropsWithChildren, memo, useState } from 'react';
8
8
 
9
9
  import {
10
10
  CHAT_PORTAL_MAX_WIDTH,
@@ -94,7 +94,9 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
94
94
  minWidth: CHAT_PORTAL_WIDTH,
95
95
  }}
96
96
  >
97
- <Flexbox className={styles.panel}>{children}</Flexbox>
97
+ <Activity mode={showPortal ? 'visible' : 'hidden'} name="GroupPortal">
98
+ <Flexbox className={styles.panel}>{children}</Flexbox>
99
+ </Activity>
98
100
  </DraggablePanelContainer>
99
101
  </DraggablePanel>
100
102
  );
@@ -1,16 +1,19 @@
1
1
  import { parse } from 'partial-json';
2
+ import { useMemo } from 'react';
2
3
  import { stringify } from 'yaml';
3
4
 
4
5
  export const useYamlArguments = (args?: string) => {
5
- if (!args) return '';
6
+ return useMemo(() => {
7
+ if (!args) return '';
6
8
 
7
- try {
8
- const obj = parse(args);
9
+ try {
10
+ const obj = parse(args);
9
11
 
10
- if (Object.keys(obj).length === 0) return '';
12
+ if (Object.keys(obj).length === 0) return '';
11
13
 
12
- return stringify(obj);
13
- } catch {
14
- return args;
15
- }
14
+ return stringify(obj);
15
+ } catch {
16
+ return args;
17
+ }
18
+ }, [args]);
16
19
  };
@@ -74,6 +74,7 @@ export const getServerGlobalConfig = async () => {
74
74
  defaultAgent: {
75
75
  config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
76
76
  },
77
+ enableBusinessFeatures: ENABLE_BUSINESS_FEATURES,
77
78
  enableEmailVerification: authEnv.AUTH_EMAIL_VERIFICATION,
78
79
  enableKlavis: !!klavisEnv.KLAVIS_API_KEY,
79
80
  enableLobehubSkill: !!(appEnv.MARKET_TRUSTED_CLIENT_SECRET && appEnv.MARKET_TRUSTED_CLIENT_ID),
@@ -89,10 +89,7 @@ class ChatGroupService {
89
89
  * Batch create virtual agents and add them to an existing group.
90
90
  * This is more efficient than calling createAgentOnly multiple times.
91
91
  */
92
- batchCreateAgentsInGroup = (
93
- groupId: string,
94
- agents: GroupMemberConfig[],
95
- ) => {
92
+ batchCreateAgentsInGroup = (groupId: string, agents: GroupMemberConfig[]) => {
96
93
  return lambdaClient.group.batchCreateAgentsInGroup.mutate({
97
94
  agents: agents as Partial<AgentItem>[],
98
95
  groupId,
@@ -3,6 +3,7 @@ import { type ServerConfigStore } from './store';
3
3
  export const featureFlagsSelectors = (s: ServerConfigStore) => s.featureFlags;
4
4
 
5
5
  export const serverConfigSelectors = {
6
+ enableBusinessFeatures: (s: ServerConfigStore) => s.serverConfig.enableBusinessFeatures || false,
6
7
  enableEmailVerification: (s: ServerConfigStore) =>
7
8
  s.serverConfig.enableEmailVerification || false,
8
9
  enableKlavis: (s: ServerConfigStore) => s.serverConfig.enableKlavis || false,