@lobehub/lobehub 2.0.0-next.264 → 2.0.0-next.266

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 (157) hide show
  1. package/.github/workflows/manual-build-desktop.yml +16 -37
  2. package/CHANGELOG.md +52 -0
  3. package/apps/desktop/native-deps.config.mjs +19 -3
  4. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +13 -0
  5. package/apps/desktop/src/main/utils/permissions.ts +86 -22
  6. package/changelog/v1.json +18 -0
  7. package/locales/ar/chat.json +1 -0
  8. package/locales/ar/modelProvider.json +20 -0
  9. package/locales/ar/models.json +33 -10
  10. package/locales/ar/plugin.json +1 -0
  11. package/locales/ar/providers.json +1 -0
  12. package/locales/ar/setting.json +2 -0
  13. package/locales/bg-BG/chat.json +1 -0
  14. package/locales/bg-BG/modelProvider.json +20 -0
  15. package/locales/bg-BG/models.json +27 -7
  16. package/locales/bg-BG/plugin.json +1 -0
  17. package/locales/bg-BG/providers.json +1 -0
  18. package/locales/bg-BG/setting.json +2 -0
  19. package/locales/de-DE/chat.json +1 -0
  20. package/locales/de-DE/modelProvider.json +20 -0
  21. package/locales/de-DE/models.json +44 -10
  22. package/locales/de-DE/plugin.json +1 -0
  23. package/locales/de-DE/providers.json +1 -0
  24. package/locales/de-DE/setting.json +2 -0
  25. package/locales/en-US/chat.json +1 -0
  26. package/locales/en-US/modelProvider.json +20 -0
  27. package/locales/en-US/models.json +10 -10
  28. package/locales/en-US/providers.json +1 -0
  29. package/locales/en-US/setting.json +2 -1
  30. package/locales/es-ES/chat.json +1 -0
  31. package/locales/es-ES/modelProvider.json +20 -0
  32. package/locales/es-ES/models.json +53 -10
  33. package/locales/es-ES/plugin.json +1 -0
  34. package/locales/es-ES/providers.json +1 -0
  35. package/locales/es-ES/setting.json +2 -0
  36. package/locales/fa-IR/chat.json +1 -0
  37. package/locales/fa-IR/modelProvider.json +20 -0
  38. package/locales/fa-IR/models.json +33 -10
  39. package/locales/fa-IR/plugin.json +1 -0
  40. package/locales/fa-IR/providers.json +1 -0
  41. package/locales/fa-IR/setting.json +2 -0
  42. package/locales/fr-FR/chat.json +1 -0
  43. package/locales/fr-FR/modelProvider.json +20 -0
  44. package/locales/fr-FR/models.json +27 -7
  45. package/locales/fr-FR/plugin.json +1 -0
  46. package/locales/fr-FR/providers.json +1 -0
  47. package/locales/fr-FR/setting.json +2 -0
  48. package/locales/it-IT/chat.json +1 -0
  49. package/locales/it-IT/modelProvider.json +20 -0
  50. package/locales/it-IT/models.json +10 -10
  51. package/locales/it-IT/plugin.json +1 -0
  52. package/locales/it-IT/providers.json +1 -0
  53. package/locales/it-IT/setting.json +2 -0
  54. package/locales/ja-JP/chat.json +1 -0
  55. package/locales/ja-JP/modelProvider.json +20 -0
  56. package/locales/ja-JP/models.json +5 -10
  57. package/locales/ja-JP/plugin.json +1 -0
  58. package/locales/ja-JP/providers.json +1 -0
  59. package/locales/ja-JP/setting.json +2 -0
  60. package/locales/ko-KR/chat.json +1 -0
  61. package/locales/ko-KR/modelProvider.json +20 -0
  62. package/locales/ko-KR/models.json +36 -10
  63. package/locales/ko-KR/plugin.json +1 -0
  64. package/locales/ko-KR/providers.json +1 -0
  65. package/locales/ko-KR/setting.json +2 -0
  66. package/locales/nl-NL/chat.json +1 -0
  67. package/locales/nl-NL/modelProvider.json +20 -0
  68. package/locales/nl-NL/models.json +35 -4
  69. package/locales/nl-NL/plugin.json +1 -0
  70. package/locales/nl-NL/providers.json +1 -0
  71. package/locales/nl-NL/setting.json +2 -0
  72. package/locales/pl-PL/chat.json +1 -0
  73. package/locales/pl-PL/modelProvider.json +20 -0
  74. package/locales/pl-PL/models.json +37 -7
  75. package/locales/pl-PL/plugin.json +1 -0
  76. package/locales/pl-PL/providers.json +1 -0
  77. package/locales/pl-PL/setting.json +2 -0
  78. package/locales/pt-BR/chat.json +1 -0
  79. package/locales/pt-BR/modelProvider.json +20 -0
  80. package/locales/pt-BR/models.json +51 -9
  81. package/locales/pt-BR/plugin.json +1 -0
  82. package/locales/pt-BR/providers.json +1 -0
  83. package/locales/pt-BR/setting.json +2 -0
  84. package/locales/ru-RU/chat.json +1 -0
  85. package/locales/ru-RU/modelProvider.json +20 -0
  86. package/locales/ru-RU/models.json +48 -7
  87. package/locales/ru-RU/plugin.json +1 -0
  88. package/locales/ru-RU/providers.json +1 -0
  89. package/locales/ru-RU/setting.json +2 -0
  90. package/locales/tr-TR/chat.json +1 -0
  91. package/locales/tr-TR/modelProvider.json +20 -0
  92. package/locales/tr-TR/models.json +48 -7
  93. package/locales/tr-TR/plugin.json +1 -0
  94. package/locales/tr-TR/providers.json +1 -0
  95. package/locales/tr-TR/setting.json +2 -0
  96. package/locales/vi-VN/chat.json +1 -0
  97. package/locales/vi-VN/modelProvider.json +20 -0
  98. package/locales/vi-VN/models.json +5 -5
  99. package/locales/vi-VN/plugin.json +1 -0
  100. package/locales/vi-VN/providers.json +1 -0
  101. package/locales/vi-VN/setting.json +2 -0
  102. package/locales/zh-CN/modelProvider.json +20 -20
  103. package/locales/zh-CN/models.json +49 -8
  104. package/locales/zh-CN/providers.json +1 -0
  105. package/locales/zh-CN/setting.json +2 -1
  106. package/locales/zh-TW/chat.json +1 -0
  107. package/locales/zh-TW/modelProvider.json +20 -0
  108. package/locales/zh-TW/models.json +29 -10
  109. package/locales/zh-TW/plugin.json +1 -0
  110. package/locales/zh-TW/providers.json +1 -0
  111. package/locales/zh-TW/setting.json +2 -0
  112. package/package.json +2 -2
  113. package/packages/database/src/models/__tests__/agent.test.ts +165 -4
  114. package/packages/database/src/models/agent.ts +46 -0
  115. package/packages/database/src/repositories/agentGroup/index.test.ts +498 -0
  116. package/packages/database/src/repositories/agentGroup/index.ts +150 -0
  117. package/packages/database/src/repositories/home/__tests__/index.test.ts +113 -1
  118. package/packages/database/src/repositories/home/index.ts +48 -67
  119. package/pnpm-workspace.yaml +1 -0
  120. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Body.tsx +1 -1
  121. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +84 -0
  122. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/CronTopicItem.tsx +1 -1
  123. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/index.tsx +23 -33
  124. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/Editing.tsx +12 -49
  125. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/index.tsx +3 -1
  126. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Editing.tsx +12 -40
  127. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +5 -1
  128. package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/index.tsx +2 -2
  129. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobCards.tsx +1 -1
  130. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobForm.tsx +1 -1
  131. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/AvailableAgentList.tsx +0 -1
  132. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/index.tsx +5 -1
  133. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +2 -6
  134. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +100 -0
  135. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -4
  136. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/useDropdownMenu.tsx +149 -0
  137. package/src/app/[variants]/(main)/home/_layout/hooks/index.ts +0 -1
  138. package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +1 -1
  139. package/src/components/InlineRename/index.tsx +121 -0
  140. package/src/features/ChatInput/InputEditor/index.tsx +1 -0
  141. package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
  142. package/src/features/NavPanel/components/NavItem.tsx +1 -1
  143. package/src/locales/default/setting.ts +2 -0
  144. package/src/server/routers/lambda/agent.ts +15 -0
  145. package/src/server/routers/lambda/agentGroup.ts +16 -0
  146. package/src/services/agent.ts +11 -0
  147. package/src/services/chatGroup/index.ts +11 -0
  148. package/src/store/agent/slices/cron/action.ts +108 -0
  149. package/src/store/agent/slices/cron/index.ts +1 -0
  150. package/src/store/agent/store.ts +3 -0
  151. package/src/store/home/slices/sidebarUI/action.test.ts +23 -22
  152. package/src/store/home/slices/sidebarUI/action.ts +37 -9
  153. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +0 -74
  154. package/src/app/[variants]/(main)/group/features/ChangelogModal.tsx +0 -11
  155. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +0 -62
  156. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionItemMenuItems.tsx +0 -238
  157. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +0 -56
@@ -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);
@@ -11,10 +11,10 @@ import { useSendMenuItems } from './useSendMenuItems';
11
11
  const leftActions: ActionKeys[] = [
12
12
  'model',
13
13
  'search',
14
- 'typo',
15
14
  'fileUpload',
15
+ 'tools',
16
16
  '---',
17
- ['tools', 'params', 'clear'],
17
+ ['typo', 'params', 'clear'],
18
18
  'mainToken',
19
19
  ];
20
20
 
@@ -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>
@@ -13,8 +13,8 @@ import { useGlobalStore } from '@/store/global';
13
13
  import { useHomeStore } from '@/store/home';
14
14
 
15
15
  import Actions from '../Item/Actions';
16
- import { useDropdownMenu } from '../Item/useDropdownMenu';
17
16
  import Editing from './Editing';
17
+ import { useGroupDropdownMenu } from './useDropdownMenu';
18
18
 
19
19
  interface GroupItemProps {
20
20
  className?: string;
@@ -85,13 +85,9 @@ const GroupItem = memo<GroupItemProps>(({ item, style, className }) => {
85
85
  return <GroupAvatar avatars={(avatar as any) || []} size={22} />;
86
86
  }, [isUpdating, avatar]);
87
87
 
88
- const dropdownMenu = useDropdownMenu({
89
- group: undefined,
88
+ const dropdownMenu = useGroupDropdownMenu({
90
89
  id,
91
- openCreateGroupModal: () => {}, // Groups don't need this
92
- parentType: 'group',
93
90
  pinned: pinned ?? false,
94
- sessionType: 'group',
95
91
  toggleEditing,
96
92
  });
97
93
 
@@ -0,0 +1,100 @@
1
+ import { Icon, type MenuProps } from '@lobehub/ui';
2
+ import { App } from 'antd';
3
+ import { LucideCopy, Pen, PictureInPicture2Icon, Pin, PinOff, Trash } from 'lucide-react';
4
+ import { useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useGlobalStore } from '@/store/global';
8
+ import { useHomeStore } from '@/store/home';
9
+
10
+ interface UseGroupDropdownMenuParams {
11
+ id: string;
12
+ pinned: boolean;
13
+ toggleEditing: (visible?: boolean) => void;
14
+ }
15
+
16
+ export const useGroupDropdownMenu = ({
17
+ id,
18
+ pinned,
19
+ toggleEditing,
20
+ }: UseGroupDropdownMenuParams): (() => MenuProps['items']) => {
21
+ const { t } = useTranslation('chat');
22
+ const { modal, message } = App.useApp();
23
+
24
+ const openAgentInNewWindow = useGlobalStore((s) => s.openAgentInNewWindow);
25
+ const [pinAgentGroup, duplicateAgentGroup, removeAgentGroup] = useHomeStore((s) => [
26
+ s.pinAgentGroup,
27
+ s.duplicateAgentGroup,
28
+ s.removeAgentGroup,
29
+ ]);
30
+
31
+ return useMemo(
32
+ () => () =>
33
+ [
34
+ {
35
+ icon: <Icon icon={pinned ? PinOff : Pin} />,
36
+ key: 'pin',
37
+ label: t(pinned ? 'pinOff' : 'pin'),
38
+ onClick: () => pinAgentGroup(id, !pinned),
39
+ },
40
+ {
41
+ icon: <Icon icon={Pen} />,
42
+ key: 'rename',
43
+ label: t('rename', { ns: 'common' }),
44
+ onClick: (info: any) => {
45
+ info.domEvent?.stopPropagation();
46
+ toggleEditing(true);
47
+ },
48
+ },
49
+ {
50
+ icon: <Icon icon={LucideCopy} />,
51
+ key: 'duplicate',
52
+ label: t('duplicate', { ns: 'common' }),
53
+ onClick: ({ domEvent }: any) => {
54
+ domEvent.stopPropagation();
55
+ duplicateAgentGroup(id);
56
+ },
57
+ },
58
+ {
59
+ icon: <Icon icon={PictureInPicture2Icon} />,
60
+ key: 'openInNewWindow',
61
+ label: t('openInNewWindow'),
62
+ onClick: ({ domEvent }: any) => {
63
+ domEvent.stopPropagation();
64
+ openAgentInNewWindow(id);
65
+ },
66
+ },
67
+ { type: 'divider' },
68
+ {
69
+ danger: true,
70
+ icon: <Icon icon={Trash} />,
71
+ key: 'delete',
72
+ label: t('delete', { ns: 'common' }),
73
+ onClick: ({ domEvent }: any) => {
74
+ domEvent.stopPropagation();
75
+ modal.confirm({
76
+ centered: true,
77
+ okButtonProps: { danger: true },
78
+ onOk: async () => {
79
+ await removeAgentGroup(id);
80
+ message.success(t('confirmRemoveGroupSuccess'));
81
+ },
82
+ title: t('confirmRemoveChatGroupItemAlert'),
83
+ });
84
+ },
85
+ },
86
+ ] as MenuProps['items'],
87
+ [
88
+ t,
89
+ pinned,
90
+ pinAgentGroup,
91
+ id,
92
+ toggleEditing,
93
+ duplicateAgentGroup,
94
+ openAgentInNewWindow,
95
+ modal,
96
+ removeAgentGroup,
97
+ message,
98
+ ],
99
+ );
100
+ };
@@ -15,9 +15,9 @@ import { useHomeStore } from '@/store/home';
15
15
 
16
16
  import { useAgentModal } from '../../ModalProvider';
17
17
  import Actions from '../Item/Actions';
18
- import { useDropdownMenu } from '../Item/useDropdownMenu';
19
18
  import Avatar from './Avatar';
20
19
  import Editing from './Editing';
20
+ import { useAgentDropdownMenu } from './useDropdownMenu';
21
21
 
22
22
  interface AgentItemProps {
23
23
  className?: string;
@@ -96,13 +96,11 @@ const AgentItem = memo<AgentItemProps>(({ item, style, className }) => {
96
96
  return <Avatar avatar={typeof avatar === 'string' ? avatar : undefined} />;
97
97
  }, [isUpdating, avatar]);
98
98
 
99
- const dropdownMenu = useDropdownMenu({
99
+ const dropdownMenu = useAgentDropdownMenu({
100
100
  group: undefined, // TODO: pass group from parent if needed
101
101
  id,
102
102
  openCreateGroupModal: handleOpenCreateGroupModal,
103
- parentType: 'agent',
104
103
  pinned: pinned ?? false,
105
- sessionType: 'agent',
106
104
  toggleEditing,
107
105
  });
108
106
 
@@ -0,0 +1,149 @@
1
+ import { SessionDefaultGroup } from '@lobechat/types';
2
+ import { Icon, type MenuProps } from '@lobehub/ui';
3
+ import { App } from 'antd';
4
+ import isEqual from 'fast-deep-equal';
5
+ import {
6
+ Check,
7
+ FolderInputIcon,
8
+ LucideCopy,
9
+ LucidePlus,
10
+ Pen,
11
+ PictureInPicture2Icon,
12
+ Pin,
13
+ PinOff,
14
+ Trash,
15
+ } from 'lucide-react';
16
+ import { useMemo } from 'react';
17
+ import { useTranslation } from 'react-i18next';
18
+
19
+ import { useGlobalStore } from '@/store/global';
20
+ import { useHomeStore } from '@/store/home';
21
+ import { homeAgentListSelectors } from '@/store/home/selectors';
22
+
23
+ interface UseAgentDropdownMenuParams {
24
+ group: string | undefined;
25
+ id: string;
26
+ openCreateGroupModal: () => void;
27
+ pinned: boolean;
28
+ toggleEditing: (visible?: boolean) => void;
29
+ }
30
+
31
+ export const useAgentDropdownMenu = ({
32
+ group,
33
+ id,
34
+ openCreateGroupModal,
35
+ pinned,
36
+ toggleEditing,
37
+ }: UseAgentDropdownMenuParams): (() => MenuProps['items']) => {
38
+ const { t } = useTranslation('chat');
39
+ const { modal, message } = App.useApp();
40
+
41
+ const openAgentInNewWindow = useGlobalStore((s) => s.openAgentInNewWindow);
42
+ const sessionCustomGroups = useHomeStore(homeAgentListSelectors.agentGroups, isEqual);
43
+ const [pinAgent, duplicateAgent, updateAgentGroup, removeAgent] = useHomeStore((s) => [
44
+ s.pinAgent,
45
+ s.duplicateAgent,
46
+ s.updateAgentGroup,
47
+ s.removeAgent,
48
+ ]);
49
+
50
+ const isDefault = group === SessionDefaultGroup.Default;
51
+
52
+ return useMemo(
53
+ () => () =>
54
+ [
55
+ {
56
+ icon: <Icon icon={pinned ? PinOff : Pin} />,
57
+ key: 'pin',
58
+ label: t(pinned ? 'pinOff' : 'pin'),
59
+ onClick: () => pinAgent(id, !pinned),
60
+ },
61
+ {
62
+ icon: <Icon icon={Pen} />,
63
+ key: 'rename',
64
+ label: t('rename', { ns: 'common' }),
65
+ onClick: (info: any) => {
66
+ info.domEvent?.stopPropagation();
67
+ toggleEditing(true);
68
+ },
69
+ },
70
+ {
71
+ icon: <Icon icon={LucideCopy} />,
72
+ key: 'duplicate',
73
+ label: t('duplicate', { ns: 'common' }),
74
+ onClick: ({ domEvent }: any) => {
75
+ domEvent.stopPropagation();
76
+ duplicateAgent(id);
77
+ },
78
+ },
79
+ {
80
+ icon: <Icon icon={PictureInPicture2Icon} />,
81
+ key: 'openInNewWindow',
82
+ label: t('openInNewWindow'),
83
+ onClick: ({ domEvent }: any) => {
84
+ domEvent.stopPropagation();
85
+ openAgentInNewWindow(id);
86
+ },
87
+ },
88
+ { type: 'divider' },
89
+ {
90
+ children: [
91
+ ...sessionCustomGroups.map(({ id: groupId, name }) => ({
92
+ icon: group === groupId ? <Icon icon={Check} /> : <div />,
93
+ key: groupId,
94
+ label: name,
95
+ onClick: () => updateAgentGroup(id, groupId),
96
+ })),
97
+ {
98
+ icon: isDefault ? <Icon icon={Check} /> : <div />,
99
+ key: 'defaultList',
100
+ label: t('defaultList'),
101
+ onClick: () => updateAgentGroup(id, SessionDefaultGroup.Default),
102
+ },
103
+ { type: 'divider' as const },
104
+ {
105
+ icon: <Icon icon={LucidePlus} />,
106
+ key: 'createGroup',
107
+ label: <div>{t('sessionGroup.createGroup')}</div>,
108
+ onClick: ({ domEvent }: any) => {
109
+ domEvent.stopPropagation();
110
+ openCreateGroupModal();
111
+ },
112
+ },
113
+ ],
114
+ icon: <Icon icon={FolderInputIcon} />,
115
+ key: 'moveGroup',
116
+ label: t('sessionGroup.moveGroup'),
117
+ },
118
+ { type: 'divider' },
119
+ {
120
+ danger: true,
121
+ icon: <Icon icon={Trash} />,
122
+ key: 'delete',
123
+ label: t('delete', { ns: 'common' }),
124
+ onClick: ({ domEvent }: any) => {
125
+ domEvent.stopPropagation();
126
+ modal.confirm({
127
+ centered: true,
128
+ okButtonProps: { danger: true },
129
+ onOk: async () => {
130
+ await removeAgent(id);
131
+ message.success(t('confirmRemoveSessionSuccess'));
132
+ },
133
+ title: t('confirmRemoveSessionItemAlert'),
134
+ });
135
+ },
136
+ },
137
+ ] as MenuProps['items'],
138
+ [
139
+ pinned,
140
+ id,
141
+ toggleEditing,
142
+ sessionCustomGroups,
143
+ group,
144
+ isDefault,
145
+ openCreateGroupModal,
146
+ message,
147
+ ],
148
+ );
149
+ };
@@ -1,4 +1,3 @@
1
1
  export { useCreateMenuItems } from './useCreateMenuItems';
2
2
  export { useProjectMenuItems } from './useProjectMenuItems';
3
3
  export { useSessionGroupMenuItems } from './useSessionGroupMenuItems';
4
- export { useSessionItemMenuItems } from './useSessionItemMenuItems';
@@ -12,7 +12,7 @@ import ModeHeader from './ModeHeader';
12
12
  import StarterList from './StarterList';
13
13
  import { useSend } from './useSend';
14
14
 
15
- const leftActions: ActionKeys[] = ['model', 'search', 'fileUpload'];
15
+ const leftActions: ActionKeys[] = ['model', 'search', 'fileUpload', 'tools'];
16
16
 
17
17
  const InputArea = () => {
18
18
  const { loading, send, inboxAgentId } = useSend();
@@ -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;
@@ -120,6 +120,7 @@ const InputEditor = memo<{ defaultRows?: number }>(({ defaultRows = 2 }) => {
120
120
  className={className}
121
121
  content={''}
122
122
  editor={editor}
123
+ pasteAsPlainText
123
124
  {...richRenderProps}
124
125
  mentionOption={
125
126
  enableMention
@@ -36,7 +36,7 @@ const styles = createStaticStyles(({ css }) => ({
36
36
  }));
37
37
 
38
38
  const useIsEditorInit = (editor: IEditor) => {
39
- const [isEditInit, setEditInit] = useState<boolean>(!!editor.getLexicalEditor());
39
+ const [isEditInit, setEditInit] = useState<boolean>(!!editor?.getLexicalEditor());
40
40
 
41
41
  useEffect(() => {
42
42
  if (!editor) return;
@@ -98,7 +98,7 @@ const NavItem = memo<NavItemProps>(
98
98
 
99
99
  <Flexbox align={'center'} flex={1} gap={8} horizontal style={{ overflow: 'hidden' }}>
100
100
  <Text color={textColor} ellipsis style={{ flex: 1 }}>
101
- {title || 'LobeHub'}
101
+ {title}
102
102
  </Text>
103
103
  <Flexbox
104
104
  align={'center'}
@@ -33,6 +33,7 @@ export default {
33
33
  'agentCronJobs.maxExecutions': 'Max Executions',
34
34
  'agentCronJobs.name': 'Task Name',
35
35
  'agentCronJobs.never': 'Never',
36
+ 'agentCronJobs.noExecutionResults': 'No execution results',
36
37
  'agentCronJobs.remainingExecutions': 'Remaining: {{count}}',
37
38
  'agentCronJobs.save': 'Save',
38
39
  'agentCronJobs.schedule': 'Schedule',
@@ -42,6 +43,7 @@ export default {
42
43
  'agentCronJobs.timeRange': 'Time Range',
43
44
  'agentCronJobs.title': 'Scheduled Tasks',
44
45
  'agentCronJobs.unlimited': 'Unlimited',
46
+ 'agentCronJobs.unnamedTask': 'Unnamed Task',
45
47
  'agentCronJobs.updateSuccess': 'Scheduled task updated successfully',
46
48
  'agentCronJobs.weekdays': 'Weekdays',
47
49
  'agentInfoDescription.basic.avatar': 'Avatar',
@@ -130,6 +130,21 @@ export const agentRouter = router({
130
130
  return ctx.agentModel.deleteAgentKnowledgeBase(input.agentId, input.knowledgeBaseId);
131
131
  }),
132
132
 
133
+ /**
134
+ * Duplicate an agent and its associated session.
135
+ * Returns the new agent ID and session ID.
136
+ */
137
+ duplicateAgent: agentProcedure
138
+ .input(
139
+ z.object({
140
+ agentId: z.string(),
141
+ newTitle: z.string().optional(),
142
+ }),
143
+ )
144
+ .mutation(async ({ input, ctx }) => {
145
+ return ctx.agentModel.duplicate(input.agentId, input.newTitle);
146
+ }),
147
+
133
148
  /**
134
149
  * Get an agent by marketIdentifier
135
150
  * @returns agent id if exists, null otherwise