@lobehub/lobehub 2.0.0-next.265 → 2.0.0-next.267

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 (143) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +14 -0
  3. package/e2e/CLAUDE.md +34 -73
  4. package/e2e/docs/local-setup.md +67 -219
  5. package/e2e/scripts/setup.ts +529 -0
  6. package/e2e/src/features/home/sidebarAgent.feature +62 -0
  7. package/e2e/src/features/home/sidebarGroup.feature +62 -0
  8. package/e2e/src/steps/home/sidebarAgent.steps.ts +373 -0
  9. package/e2e/src/steps/home/sidebarGroup.steps.ts +168 -0
  10. package/e2e/src/steps/hooks.ts +2 -0
  11. package/locales/ar/chat.json +1 -0
  12. package/locales/ar/modelProvider.json +20 -0
  13. package/locales/ar/models.json +33 -10
  14. package/locales/ar/plugin.json +1 -0
  15. package/locales/ar/providers.json +1 -0
  16. package/locales/ar/setting.json +2 -0
  17. package/locales/bg-BG/chat.json +1 -0
  18. package/locales/bg-BG/modelProvider.json +20 -0
  19. package/locales/bg-BG/models.json +27 -7
  20. package/locales/bg-BG/plugin.json +1 -0
  21. package/locales/bg-BG/providers.json +1 -0
  22. package/locales/bg-BG/setting.json +2 -0
  23. package/locales/de-DE/chat.json +1 -0
  24. package/locales/de-DE/modelProvider.json +20 -0
  25. package/locales/de-DE/models.json +44 -10
  26. package/locales/de-DE/plugin.json +1 -0
  27. package/locales/de-DE/providers.json +1 -0
  28. package/locales/de-DE/setting.json +2 -0
  29. package/locales/en-US/chat.json +1 -0
  30. package/locales/en-US/modelProvider.json +20 -0
  31. package/locales/en-US/models.json +10 -10
  32. package/locales/en-US/providers.json +1 -0
  33. package/locales/en-US/setting.json +2 -1
  34. package/locales/es-ES/chat.json +1 -0
  35. package/locales/es-ES/modelProvider.json +20 -0
  36. package/locales/es-ES/models.json +53 -10
  37. package/locales/es-ES/plugin.json +1 -0
  38. package/locales/es-ES/providers.json +1 -0
  39. package/locales/es-ES/setting.json +2 -0
  40. package/locales/fa-IR/chat.json +1 -0
  41. package/locales/fa-IR/modelProvider.json +20 -0
  42. package/locales/fa-IR/models.json +33 -10
  43. package/locales/fa-IR/plugin.json +1 -0
  44. package/locales/fa-IR/providers.json +1 -0
  45. package/locales/fa-IR/setting.json +2 -0
  46. package/locales/fr-FR/chat.json +1 -0
  47. package/locales/fr-FR/modelProvider.json +20 -0
  48. package/locales/fr-FR/models.json +27 -7
  49. package/locales/fr-FR/plugin.json +1 -0
  50. package/locales/fr-FR/providers.json +1 -0
  51. package/locales/fr-FR/setting.json +2 -0
  52. package/locales/it-IT/chat.json +1 -0
  53. package/locales/it-IT/modelProvider.json +20 -0
  54. package/locales/it-IT/models.json +10 -10
  55. package/locales/it-IT/plugin.json +1 -0
  56. package/locales/it-IT/providers.json +1 -0
  57. package/locales/it-IT/setting.json +2 -0
  58. package/locales/ja-JP/chat.json +1 -0
  59. package/locales/ja-JP/modelProvider.json +20 -0
  60. package/locales/ja-JP/models.json +5 -10
  61. package/locales/ja-JP/plugin.json +1 -0
  62. package/locales/ja-JP/providers.json +1 -0
  63. package/locales/ja-JP/setting.json +2 -0
  64. package/locales/ko-KR/chat.json +1 -0
  65. package/locales/ko-KR/modelProvider.json +20 -0
  66. package/locales/ko-KR/models.json +36 -10
  67. package/locales/ko-KR/plugin.json +1 -0
  68. package/locales/ko-KR/providers.json +1 -0
  69. package/locales/ko-KR/setting.json +2 -0
  70. package/locales/nl-NL/chat.json +1 -0
  71. package/locales/nl-NL/modelProvider.json +20 -0
  72. package/locales/nl-NL/models.json +35 -4
  73. package/locales/nl-NL/plugin.json +1 -0
  74. package/locales/nl-NL/providers.json +1 -0
  75. package/locales/nl-NL/setting.json +2 -0
  76. package/locales/pl-PL/chat.json +1 -0
  77. package/locales/pl-PL/modelProvider.json +20 -0
  78. package/locales/pl-PL/models.json +37 -7
  79. package/locales/pl-PL/plugin.json +1 -0
  80. package/locales/pl-PL/providers.json +1 -0
  81. package/locales/pl-PL/setting.json +2 -0
  82. package/locales/pt-BR/chat.json +1 -0
  83. package/locales/pt-BR/modelProvider.json +20 -0
  84. package/locales/pt-BR/models.json +51 -9
  85. package/locales/pt-BR/plugin.json +1 -0
  86. package/locales/pt-BR/providers.json +1 -0
  87. package/locales/pt-BR/setting.json +2 -0
  88. package/locales/ru-RU/chat.json +1 -0
  89. package/locales/ru-RU/modelProvider.json +20 -0
  90. package/locales/ru-RU/models.json +48 -7
  91. package/locales/ru-RU/plugin.json +1 -0
  92. package/locales/ru-RU/providers.json +1 -0
  93. package/locales/ru-RU/setting.json +2 -0
  94. package/locales/tr-TR/chat.json +1 -0
  95. package/locales/tr-TR/modelProvider.json +20 -0
  96. package/locales/tr-TR/models.json +48 -7
  97. package/locales/tr-TR/plugin.json +1 -0
  98. package/locales/tr-TR/providers.json +1 -0
  99. package/locales/tr-TR/setting.json +2 -0
  100. package/locales/vi-VN/chat.json +1 -0
  101. package/locales/vi-VN/modelProvider.json +20 -0
  102. package/locales/vi-VN/models.json +5 -5
  103. package/locales/vi-VN/plugin.json +1 -0
  104. package/locales/vi-VN/providers.json +1 -0
  105. package/locales/vi-VN/setting.json +2 -0
  106. package/locales/zh-CN/modelProvider.json +20 -20
  107. package/locales/zh-CN/models.json +49 -8
  108. package/locales/zh-CN/providers.json +1 -0
  109. package/locales/zh-CN/setting.json +2 -1
  110. package/locales/zh-TW/chat.json +1 -0
  111. package/locales/zh-TW/modelProvider.json +20 -0
  112. package/locales/zh-TW/models.json +29 -10
  113. package/locales/zh-TW/plugin.json +1 -0
  114. package/locales/zh-TW/providers.json +1 -0
  115. package/locales/zh-TW/setting.json +2 -0
  116. package/package.json +3 -3
  117. package/packages/utils/src/multimodalContent.test.ts +302 -0
  118. package/packages/utils/src/server/__tests__/sse.test.ts +353 -0
  119. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Body.tsx +1 -1
  120. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +84 -0
  121. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/CronTopicItem.tsx +1 -1
  122. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/index.tsx +23 -33
  123. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/Editing.tsx +12 -49
  124. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/index.tsx +3 -1
  125. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Editing.tsx +12 -40
  126. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +5 -1
  127. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobCards.tsx +1 -1
  128. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobForm.tsx +1 -1
  129. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/AvailableAgentList.tsx +0 -1
  130. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/index.tsx +5 -1
  131. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/Editing.tsx +4 -11
  132. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +3 -3
  133. package/src/components/InlineRename/index.tsx +121 -0
  134. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +42 -7
  135. package/src/features/NavPanel/components/NavItem.tsx +1 -1
  136. package/src/locales/default/setting.ts +2 -0
  137. package/src/store/agent/slices/cron/action.ts +108 -0
  138. package/src/store/agent/slices/cron/index.ts +1 -0
  139. package/src/store/agent/store.ts +3 -0
  140. package/src/store/home/slices/sidebarUI/action.ts +9 -0
  141. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +0 -74
  142. package/src/app/[variants]/(main)/group/features/ChangelogModal.tsx +0 -11
  143. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +0 -56
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import { AccordionItem, ActionIcon, Flexbox, Icon, Text } from '@lobehub/ui';
4
+ import { Settings2Icon, TimerIcon, TimerOffIcon } from 'lucide-react';
5
+ import { memo, useCallback } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { useParams } from 'react-router-dom';
8
+
9
+ import { useRouter } from '@/app/[variants]/(main)/hooks/useRouter';
10
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
+
12
+ import CronTopicItem from './CronTopicItem';
13
+
14
+ interface CronTopicGroupProps {
15
+ cronJob: AgentCronJob | null;
16
+ cronJobId: string;
17
+ topics: Array<{
18
+ createdAt: Date | string;
19
+ favorite?: boolean | null;
20
+ historySummary?: string | null;
21
+ id: string;
22
+ metadata?: any;
23
+ title?: string | null;
24
+ trigger?: string | null;
25
+ updatedAt: Date | string;
26
+ }>;
27
+ }
28
+
29
+ const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }) => {
30
+ const { t } = useTranslation('setting');
31
+ const { aid, cronId } = useParams<{ aid?: string; cronId?: string }>();
32
+ const router = useRouter();
33
+
34
+ const handleOpenCronJob = useCallback(() => {
35
+ if (!aid) return;
36
+ router.push(`/agent/${aid}/cron/${cronJobId}`);
37
+ }, [aid, cronJobId, router]);
38
+
39
+ const cronJobName = cronJob?.name || t('agentCronJobs.unnamedTask');
40
+ const isEnabled = cronJob?.enabled ?? false;
41
+ const isActive = cronId === cronJobId;
42
+
43
+ return (
44
+ <AccordionItem
45
+ action={
46
+ <ActionIcon
47
+ icon={Settings2Icon}
48
+ onClick={handleOpenCronJob}
49
+ size="small"
50
+ title={t('agentCronJobs.editJob')}
51
+ />
52
+ }
53
+ itemKey={cronJobId}
54
+ paddingBlock={4}
55
+ paddingInline={'8px 4px'}
56
+ title={
57
+ <Flexbox align="center" gap={6} height={24} horizontal style={{ overflow: 'hidden' }}>
58
+ <Icon icon={isEnabled ? TimerIcon : TimerOffIcon} style={{ opacity: 0.5 }} />
59
+ <Text ellipsis style={{ flex: 1 }} type={isActive ? undefined : 'secondary'}>
60
+ {cronJobName}
61
+ </Text>
62
+ {topics.length > 0 && (
63
+ <Text fontSize={11} type="secondary">
64
+ {topics.length}
65
+ </Text>
66
+ )}
67
+ </Flexbox>
68
+ }
69
+ variant={isActive ? 'filled' : 'borderless'}
70
+ >
71
+ <Flexbox gap={1} paddingBlock={1}>
72
+ {topics.length > 0 ? (
73
+ topics.map((topic) => <CronTopicItem key={topic.id} topic={topic} />)
74
+ ) : (
75
+ <Text fontSize={12} style={{ padding: '8px 12px' }} type="secondary">
76
+ {t('agentCronJobs.noExecutionResults')}
77
+ </Text>
78
+ )}
79
+ </Flexbox>
80
+ </AccordionItem>
81
+ );
82
+ });
83
+
84
+ export default CronTopicGroup;
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { useChatStore } from '@/store/chat';
7
7
 
8
- import TopicItem from '../List/Item';
8
+ import TopicItem from '../Topic/List/Item';
9
9
 
10
10
  interface CronTopicItemProps {
11
11
  topic: {
@@ -1,9 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
- import { AccordionItem, ActionIcon, Flexbox, Icon, Text } from '@lobehub/ui';
5
- import { message } from 'antd';
6
- import { Calendar, Plus } from 'lucide-react';
4
+ import { Accordion, AccordionItem, ActionIcon, Flexbox, Text } from '@lobehub/ui';
5
+ import { Plus } from 'lucide-react';
7
6
  import { memo, useCallback } from 'react';
8
7
  import { useTranslation } from 'react-i18next';
9
8
  import urlJoin from 'url-join';
@@ -11,9 +10,7 @@ import urlJoin from 'url-join';
11
10
  import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
12
11
  import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
13
12
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
14
- import { useFetchCronTopicsWithJobInfo } from '@/hooks/useFetchCronTopicsWithJobInfo';
15
13
  import { useQueryRoute } from '@/hooks/useQueryRoute';
16
- import { agentCronJobService } from '@/services/agentCronJob';
17
14
  import { useAgentStore } from '@/store/agent';
18
15
 
19
16
  import CronTopicGroup from './CronTopicGroup';
@@ -25,33 +22,22 @@ interface CronTopicListProps {
25
22
  const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
26
23
  const { t } = useTranslation('setting');
27
24
  const router = useQueryRoute();
28
- const agentId = useAgentStore((s) => s.activeAgentId);
29
- const { cronTopicsGroupsWithJobInfo, isLoading, mutate } = useFetchCronTopicsWithJobInfo();
30
- const totalTopics = cronTopicsGroupsWithJobInfo.reduce(
31
- (acc, group) => acc + group.topics.length,
32
- 0,
33
- );
25
+ const [agentId, createAgentCronJob, useFetchCronTopicsWithJobInfo] = useAgentStore((s) => [
26
+ s.activeAgentId,
27
+ s.createAgentCronJob,
28
+ s.useFetchCronTopicsWithJobInfo,
29
+ ]);
30
+ const { data: cronTopicsGroupsWithJobInfo = [], isLoading } =
31
+ useFetchCronTopicsWithJobInfo(agentId);
34
32
 
35
33
  const handleCreateCronJob = useCallback(async () => {
36
34
  if (!agentId) return;
37
- try {
38
- const result = await agentCronJobService.create({
39
- agentId,
40
- content: t('agentCronJobs.form.content.placeholder') || 'This is a cron job',
41
- cronPattern: '*/30 * * * *',
42
- enabled: true,
43
- name: t('agentCronJobs.addJob') || 'Cron Job Task',
44
- });
45
35
 
46
- if (result.success) {
47
- await mutate();
48
- router.push(urlJoin('/agent', agentId, 'cron', result.data.id));
49
- }
50
- } catch (error) {
51
- console.error('Failed to create cron job:', error);
52
- message.error('Failed to create scheduled task');
36
+ const cronJobId = await createAgentCronJob();
37
+ if (cronJobId) {
38
+ router.push(urlJoin('/agent', agentId, 'cron', cronJobId));
53
39
  }
54
- }, [agentId, mutate, router, t]);
40
+ }, [agentId, createAgentCronJob, router]);
55
41
 
56
42
  if (!ENABLE_BUSINESS_FEATURES) return null;
57
43
 
@@ -74,7 +60,6 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
74
60
  paddingInline={'8px 4px'}
75
61
  title={
76
62
  <Flexbox align="center" gap={4} horizontal>
77
- <Icon icon={Calendar} size={12} />
78
63
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
79
64
  {t('agentCronJobs.title')}
80
65
  </Text>
@@ -96,7 +81,6 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
96
81
  paddingInline={'8px 4px'}
97
82
  title={
98
83
  <Flexbox align="center" gap={4} horizontal>
99
- <Icon icon={Calendar} size={12} />
100
84
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
101
85
  {t('agentCronJobs.title')}
102
86
  </Text>
@@ -108,6 +92,8 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
108
92
  );
109
93
  }
110
94
 
95
+ const totalCronJobs = cronTopicsGroupsWithJobInfo.length;
96
+
111
97
  return (
112
98
  <AccordionItem
113
99
  action={addAction}
@@ -116,14 +102,18 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
116
102
  paddingInline={'8px 4px'}
117
103
  title={
118
104
  <Flexbox align="center" gap={4} horizontal>
119
- <Icon icon={Calendar} size={12} />
120
105
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
121
- {`${t('agentCronJobs.title')} ${totalTopics > 0 ? totalTopics : ''}`}
106
+ {t('agentCronJobs.title')}
122
107
  </Text>
108
+ {totalCronJobs > 0 && (
109
+ <Text fontSize={11} type="secondary">
110
+ {totalCronJobs}
111
+ </Text>
112
+ )}
123
113
  </Flexbox>
124
114
  }
125
115
  >
126
- <Flexbox gap={2} paddingBlock={2}>
116
+ <Accordion defaultExpandedKeys={cronTopicsGroupsWithJobInfo.map((g) => g.cronJobId)} gap={2}>
127
117
  {cronTopicsGroupsWithJobInfo.map((group) => (
128
118
  <CronTopicGroup
129
119
  cronJob={group.cronJob}
@@ -132,7 +122,7 @@ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
132
122
  topics={group.topics}
133
123
  />
134
124
  ))}
135
- </Flexbox>
125
+ </Accordion>
136
126
  </AccordionItem>
137
127
  );
138
128
  });
@@ -1,7 +1,6 @@
1
- import { Input, type InputProps, Popover } from '@lobehub/ui';
2
- import type { InputRef } from 'antd';
3
- import { memo, useCallback, useEffect, useRef, useState } from 'react';
1
+ import { memo, useCallback } from 'react';
4
2
 
3
+ import InlineRename from '@/components/InlineRename';
5
4
  import { useChatStore } from '@/store/chat';
6
5
 
7
6
  interface EditingProps {
@@ -10,27 +9,14 @@ interface EditingProps {
10
9
  toggleEditing: (visible?: boolean) => void;
11
10
  }
12
11
 
13
- function FocusableInput({ ...props }: InputProps) {
14
- const ref = useRef<InputRef>(null);
15
- useEffect(() => {
16
- queueMicrotask(() => {
17
- if (ref.current) {
18
- ref.current.input?.focus();
19
- }
20
- });
21
- }, []);
22
- return <Input {...props} ref={ref} />;
23
- }
24
-
25
12
  const Editing = memo<EditingProps>(({ id, title, toggleEditing }) => {
26
- const [newTitle, setNewTitle] = useState(title);
27
13
  const [editing, updateTopicTitle] = useChatStore((s) => [
28
14
  s.topicRenamingId === id,
29
15
  s.updateTopicTitle,
30
16
  ]);
31
17
 
32
- const handleUpdate = useCallback(async () => {
33
- if (newTitle && title !== newTitle) {
18
+ const handleSave = useCallback(
19
+ async (newTitle: string) => {
34
20
  try {
35
21
  // Set loading state
36
22
  useChatStore.setState(
@@ -53,40 +39,17 @@ const Editing = memo<EditingProps>(({ id, title, toggleEditing }) => {
53
39
  'clearTopicUpdating',
54
40
  );
55
41
  }
56
- }
57
- }, [newTitle, title, id, updateTopicTitle, toggleEditing]);
42
+ },
43
+ [id, updateTopicTitle],
44
+ );
58
45
 
59
46
  return (
60
- <Popover
61
- content={
62
- <FocusableInput
63
- defaultValue={title}
64
- onBlur={handleUpdate}
65
- onChange={(e) => setNewTitle(e.target.value)}
66
- onClick={(e) => e.stopPropagation()}
67
- onPressEnter={() => {
68
- handleUpdate();
69
- toggleEditing(false);
70
- }}
71
- />
72
- }
73
- onOpenChange={(open) => {
74
- if (!open) handleUpdate();
75
-
76
- toggleEditing(open);
77
- }}
47
+ <InlineRename
48
+ onOpenChange={(open) => toggleEditing(open)}
49
+ onSave={handleSave}
78
50
  open={editing}
79
- placement="bottomLeft"
80
- styles={{
81
- content: {
82
- padding: 4,
83
- width: 320,
84
- },
85
- }}
86
- trigger="click"
87
- >
88
- <div />
89
- </Popover>
51
+ title={title}
52
+ />
90
53
  );
91
54
  });
92
55
 
@@ -18,6 +18,8 @@ import AllTopicsDrawer from '../AllTopicsDrawer';
18
18
  import ByTimeMode from '../TopicListContent/ByTimeMode';
19
19
  import FlatMode from '../TopicListContent/FlatMode';
20
20
 
21
+ const fetchParams = { excludeTriggers: ['cron'] };
22
+
21
23
  const TopicList = memo(() => {
22
24
  const { t } = useTranslation('topic');
23
25
  const router = useQueryRoute();
@@ -32,7 +34,7 @@ const TopicList = memo(() => {
32
34
 
33
35
  const [topicDisplayMode] = useUserStore((s) => [preferenceSelectors.topicDisplayMode(s)]);
34
36
 
35
- useFetchTopics({ excludeTriggers: ['cron'] });
37
+ useFetchTopics(fetchParams);
36
38
 
37
39
  // Show skeleton when current session's topic data is not yet loaded
38
40
  if (isUndefinedTopics) return <SkeletonList />;
@@ -1,6 +1,6 @@
1
- import { Input, Popover } from '@lobehub/ui';
2
- import { memo, useCallback, useState } from 'react';
1
+ import { memo, useCallback } from 'react';
3
2
 
3
+ import InlineRename from '@/components/InlineRename';
4
4
  import { useChatStore } from '@/store/chat';
5
5
 
6
6
  interface EditingProps {
@@ -10,53 +10,25 @@ interface EditingProps {
10
10
  }
11
11
 
12
12
  const Editing = memo<EditingProps>(({ id, title, toggleEditing }) => {
13
- const [newTitle, setNewTitle] = useState(title);
14
13
  const [editing, updateThreadTitle] = useChatStore((s) => [
15
14
  s.threadRenamingId === id,
16
15
  s.updateThreadTitle,
17
16
  ]);
18
17
 
19
- const handleUpdate = useCallback(async () => {
20
- if (newTitle && title !== newTitle) {
18
+ const handleSave = useCallback(
19
+ async (newTitle: string) => {
21
20
  await updateThreadTitle(id, newTitle);
22
- }
23
- toggleEditing(false);
24
- }, [newTitle, title, id, updateThreadTitle, toggleEditing]);
21
+ },
22
+ [id, updateThreadTitle],
23
+ );
25
24
 
26
25
  return (
27
- <Popover
28
- content={
29
- <Input
30
- autoFocus
31
- defaultValue={title}
32
- onBlur={() => {
33
- handleUpdate();
34
- toggleEditing(false);
35
- }}
36
- onChange={(e) => setNewTitle(e.target.value)}
37
- onClick={(e) => e.stopPropagation()}
38
- onPressEnter={() => {
39
- handleUpdate();
40
- toggleEditing(false);
41
- }}
42
- />
43
- }
44
- onOpenChange={(open) => {
45
- if (!open) handleUpdate();
46
- toggleEditing(open);
47
- }}
26
+ <InlineRename
27
+ onOpenChange={(open) => toggleEditing(open)}
28
+ onSave={handleSave}
48
29
  open={editing}
49
- placement="bottomLeft"
50
- styles={{
51
- content: {
52
- padding: 4,
53
- width: 320,
54
- },
55
- }}
56
- trigger="click"
57
- >
58
- <div />
59
- </Popover>
30
+ title={title}
31
+ />
60
32
  );
61
33
  });
62
34
 
@@ -32,7 +32,11 @@ export const useTopicNavigation = () => {
32
32
  (topicId?: string) => {
33
33
  // If in agent sub-route, navigate back to agent chat first
34
34
  if (isInAgentSubRoute() && activeAgentId) {
35
- router.push(urlJoin('/agent', activeAgentId as string));
35
+ const basePath = urlJoin('/agent', activeAgentId as string);
36
+ // Include topicId in URL when navigating from sub-route
37
+ router.push(topicId ? `${basePath}?topic=${topicId}` : basePath);
38
+ toggleConfig(false);
39
+ return;
36
40
  }
37
41
 
38
42
  switchTopic(topicId);
@@ -99,7 +99,7 @@ const CronJobCards = memo<CronJobCardsProps>(({ cronJobs, loading, onDelete, onE
99
99
  whiteSpace: 'nowrap',
100
100
  }}
101
101
  >
102
- {job.name || 'Unnamed Task'}
102
+ {job.name || t('agentCronJobs.unnamedTask')}
103
103
  </span>
104
104
  <Badge status={statusInfo.status} />
105
105
  </Flexbox>
@@ -96,7 +96,7 @@ const CronJobForm = memo<CronJobFormProps>(({ editingJob, formRef, onSubmit }) =
96
96
  const data: CronJobFormData = {
97
97
  content: values.content,
98
98
  cronPattern: values.cronPattern,
99
- enabled: true,
99
+ enabled: false,
100
100
  executionConditions: Object.keys(executionConditions).length > 0 ? executionConditions : null,
101
101
  maxExecutions: values.maxExecutions || null,
102
102
  name: values.name,
@@ -21,7 +21,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
21
21
 
22
22
  padding-block: ${cssVar.paddingSM}px 0;
23
23
  padding-inline: ${cssVar.paddingSM}px;
24
- border-inline-end: 1px solid ${cssVar.colorBorderSecondary};
25
24
  `,
26
25
  }));
27
26
 
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Button, Flexbox, Modal } from '@lobehub/ui';
4
+ import { Divider } from 'antd';
4
5
  import { createStaticStyles } from 'antd-style';
5
6
  import { memo, useEffect, useMemo, useState } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
@@ -19,6 +20,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
19
20
  flex-direction: row;
20
21
 
21
22
  height: 500px;
23
+ padding: 12px;
22
24
  border: 1px solid ${cssVar.colorBorderSecondary};
23
25
  border-radius: ${cssVar.borderRadius}px;
24
26
  `,
@@ -104,10 +106,12 @@ const AddGroupMemberModal = memo<AddGroupMemberModalProps>(
104
106
  title={t('memberSelection.addMember')}
105
107
  width={800}
106
108
  >
107
- <Flexbox className={styles.container} horizontal>
109
+ <Flexbox className={styles.container} gap={8} horizontal>
108
110
  {/* Left Column - Available Agents */}
109
111
  <AvailableAgentList agents={availableAgents} isLoading={isLoadingAgents} />
110
112
 
113
+ <Divider orientation={'vertical'} style={{ height: '100%' }} />
114
+
111
115
  {/* Right Column - Selected Agents */}
112
116
  <SelectedAgentList agents={allAgents} />
113
117
  </Flexbox>
@@ -10,7 +10,7 @@ interface EditingProps {
10
10
  }
11
11
 
12
12
  const Editing = memo<EditingProps>(({ id, title, toggleEditing }) => {
13
- const editing = useHomeStore((s) => s.agentRenamingId === id);
13
+ const editing = useHomeStore((s) => s.groupRenamingId === id);
14
14
 
15
15
  const [newTitle, setNewTitle] = useState(title);
16
16
 
@@ -19,17 +19,10 @@ const Editing = memo<EditingProps>(({ id, title, toggleEditing }) => {
19
19
 
20
20
  if (hasChanges) {
21
21
  try {
22
- // Set loading state
23
- useHomeStore.getState().setAgentUpdatingId(id);
24
-
25
- // TODO: Add group title update logic here
26
- // await updateGroupTitle(id, newTitle);
27
-
28
- // Refresh agent list to update sidebar display
29
- await useHomeStore.getState().refreshAgentList();
22
+ useHomeStore.getState().setGroupUpdatingId(id);
23
+ await useHomeStore.getState().renameAgentGroup(id, newTitle);
30
24
  } finally {
31
- // Clear loading state
32
- useHomeStore.getState().setAgentUpdatingId(null);
25
+ useHomeStore.getState().setGroupUpdatingId(null);
33
26
  }
34
27
  }
35
28
  toggleEditing(false);
@@ -30,8 +30,8 @@ const GroupItem = memo<GroupItemProps>(({ item, style, className }) => {
30
30
 
31
31
  // Get UI state from homeStore (editing, updating)
32
32
  const [editing, isUpdating] = useHomeStore((s) => [
33
- s.agentRenamingId === id,
34
- s.agentUpdatingId === id,
33
+ s.groupRenamingId === id,
34
+ s.groupUpdatingId === id,
35
35
  ]);
36
36
 
37
37
  // Get display title with fallback
@@ -63,7 +63,7 @@ const GroupItem = memo<GroupItemProps>(({ item, style, className }) => {
63
63
 
64
64
  const toggleEditing = useCallback(
65
65
  (visible?: boolean) => {
66
- useHomeStore.getState().setAgentRenamingId(visible ? id : null);
66
+ useHomeStore.getState().setGroupRenamingId(visible ? id : null);
67
67
  },
68
68
  [id],
69
69
  );
@@ -0,0 +1,121 @@
1
+ 'use client';
2
+
3
+ import { Input, type InputProps, Popover } from '@lobehub/ui';
4
+ import type { InputRef, PopoverProps } from 'antd';
5
+ import { KeyboardEvent, memo, useCallback, useEffect, useRef, useState } from 'react';
6
+
7
+ function FocusableInput(props: InputProps) {
8
+ const ref = useRef<InputRef>(null);
9
+ useEffect(() => {
10
+ queueMicrotask(() => {
11
+ ref.current?.input?.focus();
12
+ });
13
+ }, []);
14
+ return <Input {...props} ref={ref} />;
15
+ }
16
+
17
+ export interface InlineRenameProps {
18
+ /**
19
+ * Callback when editing is cancelled (Escape key)
20
+ */
21
+ onCancel?: () => void;
22
+ /**
23
+ * Callback when open state changes
24
+ */
25
+ onOpenChange: (open: boolean) => void;
26
+ /**
27
+ * Callback to save the new title
28
+ */
29
+ onSave: (newTitle: string) => void | Promise<void>;
30
+ /**
31
+ * Whether the popover is open (editing mode)
32
+ */
33
+ open: boolean;
34
+ /**
35
+ * Popover placement
36
+ */
37
+ placement?: PopoverProps['placement'];
38
+ /**
39
+ * Current title
40
+ */
41
+ title: string;
42
+ /**
43
+ * Popover width
44
+ */
45
+ width?: number;
46
+ }
47
+
48
+ const InlineRename = memo<InlineRenameProps>(
49
+ ({ open, title, onOpenChange, onSave, onCancel, placement = 'bottomLeft', width = 320 }) => {
50
+ const [newTitle, setNewTitle] = useState(title);
51
+ const savedRef = useRef(false);
52
+
53
+ // Reset state when opening
54
+ useEffect(() => {
55
+ if (open) {
56
+ setNewTitle(title);
57
+ savedRef.current = false;
58
+ }
59
+ }, [open, title]);
60
+
61
+ const handleSave = useCallback(async () => {
62
+ if (savedRef.current) return;
63
+
64
+ if (newTitle && title !== newTitle) {
65
+ savedRef.current = true;
66
+ await onSave(newTitle);
67
+ }
68
+ }, [newTitle, title, onSave]);
69
+
70
+ const handleClose = useCallback(() => {
71
+ onOpenChange(false);
72
+ }, [onOpenChange]);
73
+
74
+ const handleKeyDown = useCallback(
75
+ (e: KeyboardEvent) => {
76
+ if (e.key === 'Escape') {
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+ onCancel?.();
80
+ handleClose();
81
+ }
82
+ },
83
+ [onCancel, handleClose],
84
+ );
85
+
86
+ return (
87
+ <Popover
88
+ content={
89
+ <FocusableInput
90
+ defaultValue={title}
91
+ onBlur={handleSave}
92
+ onChange={(e) => setNewTitle(e.target.value)}
93
+ onClick={(e) => e.stopPropagation()}
94
+ onKeyDown={handleKeyDown}
95
+ onPressEnter={() => {
96
+ handleSave();
97
+ handleClose();
98
+ }}
99
+ />
100
+ }
101
+ onOpenChange={(nextOpen) => {
102
+ if (!nextOpen) handleSave();
103
+ onOpenChange(nextOpen);
104
+ }}
105
+ open={open}
106
+ placement={placement}
107
+ styles={{
108
+ content: {
109
+ padding: 4,
110
+ width,
111
+ },
112
+ }}
113
+ trigger="click"
114
+ >
115
+ <div />
116
+ </Popover>
117
+ );
118
+ },
119
+ );
120
+
121
+ export default InlineRename;