@lobehub/lobehub 2.0.0-next.322 → 2.0.0-next.324

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 (111) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +9 -76
  3. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +0 -1
  4. package/apps/desktop/src/main/modules/updater/configs.ts +0 -4
  5. package/changelog/v1.json +24 -0
  6. package/e2e/src/mocks/llm/index.ts +3 -3
  7. package/locales/ar/common.json +5 -0
  8. package/locales/ar/error.json +10 -1
  9. package/locales/bg-BG/common.json +5 -0
  10. package/locales/bg-BG/error.json +10 -1
  11. package/locales/de-DE/common.json +5 -0
  12. package/locales/de-DE/error.json +10 -1
  13. package/locales/en-US/common.json +5 -0
  14. package/locales/es-ES/common.json +5 -0
  15. package/locales/es-ES/error.json +10 -1
  16. package/locales/fa-IR/common.json +5 -0
  17. package/locales/fa-IR/error.json +10 -1
  18. package/locales/fr-FR/common.json +5 -0
  19. package/locales/fr-FR/error.json +10 -1
  20. package/locales/it-IT/common.json +5 -0
  21. package/locales/it-IT/error.json +10 -1
  22. package/locales/ja-JP/common.json +5 -0
  23. package/locales/ja-JP/error.json +10 -1
  24. package/locales/ko-KR/common.json +5 -0
  25. package/locales/ko-KR/error.json +10 -1
  26. package/locales/nl-NL/common.json +5 -0
  27. package/locales/nl-NL/error.json +10 -1
  28. package/locales/pl-PL/common.json +5 -0
  29. package/locales/pl-PL/error.json +10 -1
  30. package/locales/pt-BR/common.json +5 -0
  31. package/locales/pt-BR/error.json +10 -1
  32. package/locales/ru-RU/common.json +5 -0
  33. package/locales/ru-RU/error.json +10 -1
  34. package/locales/tr-TR/common.json +5 -0
  35. package/locales/tr-TR/error.json +10 -1
  36. package/locales/vi-VN/common.json +5 -0
  37. package/locales/vi-VN/error.json +10 -1
  38. package/locales/zh-CN/common.json +5 -0
  39. package/locales/zh-TW/common.json +5 -0
  40. package/locales/zh-TW/error.json +10 -1
  41. package/package.json +2 -2
  42. package/packages/business/const/src/branding.ts +1 -0
  43. package/packages/business/const/src/llm.ts +2 -1
  44. package/packages/const/src/settings/llm.ts +2 -1
  45. package/packages/const/src/settings/systemAgent.ts +12 -7
  46. package/packages/database/src/models/agent.ts +18 -1
  47. package/packages/database/src/models/chatGroup.ts +18 -1
  48. package/packages/database/src/types/chatGroup.ts +1 -0
  49. package/packages/model-bank/package.json +1 -1
  50. package/packages/model-bank/src/aiModels/index.ts +2 -2
  51. package/packages/model-bank/src/aiModels/lobehub/chat/anthropic.ts +256 -0
  52. package/packages/model-bank/src/aiModels/lobehub/chat/deepseek.ts +45 -0
  53. package/packages/model-bank/src/aiModels/lobehub/chat/google.ts +267 -0
  54. package/packages/model-bank/src/aiModels/lobehub/chat/index.ts +26 -0
  55. package/packages/model-bank/src/aiModels/lobehub/chat/minimax.ts +75 -0
  56. package/packages/model-bank/src/aiModels/lobehub/chat/moonshot.ts +28 -0
  57. package/packages/model-bank/src/aiModels/lobehub/chat/openai.ts +345 -0
  58. package/packages/model-bank/src/aiModels/lobehub/chat/xai.ts +32 -0
  59. package/packages/model-bank/src/aiModels/lobehub/image.ts +240 -0
  60. package/packages/model-bank/src/aiModels/lobehub/index.ts +10 -0
  61. package/packages/model-bank/src/aiModels/lobehub/utils.ts +58 -0
  62. package/packages/model-bank/src/modelProviders/index.ts +10 -10
  63. package/packages/model-runtime/src/core/streams/qwen.test.ts +320 -0
  64. package/packages/model-runtime/src/core/streams/qwen.ts +19 -10
  65. package/packages/types/package.json +1 -1
  66. package/packages/types/src/agentGroup/index.ts +2 -0
  67. package/packages/types/src/discover/assistants.ts +9 -0
  68. package/packages/types/src/discover/fork.ts +163 -0
  69. package/packages/types/src/discover/groupAgents.ts +13 -4
  70. package/packages/types/src/discover/index.ts +9 -0
  71. package/src/app/[variants]/(auth)/_layout/index.tsx +2 -1
  72. package/src/app/[variants]/(auth)/auth-error/page.tsx +5 -5
  73. package/src/app/[variants]/(desktop)/desktop-onboarding/components/LobeMessage.tsx +5 -0
  74. package/src/app/[variants]/(desktop)/desktop-onboarding/features/WelcomeStep.tsx +3 -1
  75. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -2
  76. package/src/app/[variants]/(main)/community/(detail)/agent/features/Header.tsx +37 -0
  77. package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/ActionButton/ForkAndChat.tsx +133 -0
  78. package/src/app/[variants]/(main)/community/(detail)/agent/features/Sidebar/ActionButton/index.tsx +2 -2
  79. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/index.tsx +7 -10
  80. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +208 -0
  81. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/index.tsx +2 -2
  82. package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +2 -0
  83. package/src/app/[variants]/(main)/community/(detail)/user/features/UserContent.tsx +7 -0
  84. package/src/app/[variants]/(main)/community/(detail)/user/features/UserForkedAgentGroups.tsx +63 -0
  85. package/src/app/[variants]/(main)/community/(detail)/user/features/UserForkedAgents.tsx +61 -0
  86. package/src/app/[variants]/(main)/community/(detail)/user/index.tsx +3 -1
  87. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -2
  88. package/src/app/[variants]/(main)/home/features/WelcomeText/index.tsx +3 -1
  89. package/src/app/[variants]/(main)/settings/profile/index.tsx +92 -68
  90. package/src/app/[variants]/(mobile)/chat/features/Topic/index.tsx +2 -1
  91. package/src/app/[variants]/onboarding/components/LobeMessage.tsx +5 -0
  92. package/src/app/[variants]/onboarding/features/TelemetryStep.tsx +3 -1
  93. package/src/features/CommandMenu/AskAgentCommands.tsx +105 -0
  94. package/src/features/CommandMenu/CommandMenuContext.tsx +57 -38
  95. package/src/features/CommandMenu/components/CommandInput.tsx +43 -9
  96. package/src/features/CommandMenu/index.tsx +89 -27
  97. package/src/features/CommandMenu/types.ts +6 -0
  98. package/src/features/CommandMenu/useCommandMenu.ts +62 -39
  99. package/src/locales/default/common.ts +5 -0
  100. package/src/locales/default/discover.ts +371 -0
  101. package/src/server/globalConfig/parseMemoryExtractionConfig.ts +7 -8
  102. package/src/server/routers/lambda/agent.ts +14 -0
  103. package/src/server/routers/lambda/agentGroup.ts +19 -3
  104. package/src/server/routers/lambda/market/agent.ts +234 -26
  105. package/src/server/routers/lambda/market/agentGroup.ts +204 -1
  106. package/src/server/services/discover/index.ts +52 -2
  107. package/src/services/agent.ts +8 -0
  108. package/src/services/chatGroup/index.ts +8 -0
  109. package/src/services/marketApi.ts +78 -0
  110. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +12 -12
  111. package/packages/model-bank/src/aiModels/lobehub.ts +0 -1315
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+
3
+ import { Flexbox, Grid, Tag, Text } from '@lobehub/ui';
4
+ import { Pagination } from 'antd';
5
+ import { GitForkIcon } from 'lucide-react';
6
+ import { memo, useMemo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { type DiscoverGroupAgentItem } from '@/types/discover';
10
+
11
+ import UserGroupCard from './UserGroupCard';
12
+
13
+ interface UserForkedAgentGroupsProps {
14
+ agentGroups?: DiscoverGroupAgentItem[];
15
+ pageSize?: number;
16
+ rows?: number;
17
+ }
18
+
19
+ const UserForkedAgentGroups = memo<UserForkedAgentGroupsProps>(
20
+ ({ agentGroups = [], rows = 4, pageSize = 10 }) => {
21
+ const { t } = useTranslation('discover');
22
+ const [currentPage, setCurrentPage] = useState(1);
23
+
24
+ const paginatedGroups = useMemo(() => {
25
+ const startIndex = (currentPage - 1) * pageSize;
26
+ return agentGroups.slice(startIndex, startIndex + pageSize);
27
+ }, [agentGroups, currentPage, pageSize]);
28
+
29
+ if (agentGroups.length === 0) return null;
30
+
31
+ const showPagination = agentGroups.length > pageSize;
32
+
33
+ return (
34
+ <Flexbox gap={16}>
35
+ <Flexbox align={'center'} gap={8} horizontal>
36
+ <GitForkIcon size={16} />
37
+ <Text fontSize={16} weight={500}>
38
+ {t('user.forkedAgentGroups')}
39
+ </Text>
40
+ <Tag>{agentGroups.length}</Tag>
41
+ </Flexbox>
42
+ <Grid rows={rows} width={'100%'}>
43
+ {paginatedGroups.map((group, index) => (
44
+ <UserGroupCard key={group.identifier || index} {...group} />
45
+ ))}
46
+ </Grid>
47
+ {showPagination && (
48
+ <Flexbox align={'center'} justify={'center'}>
49
+ <Pagination
50
+ current={currentPage}
51
+ onChange={(page) => setCurrentPage(page)}
52
+ pageSize={pageSize}
53
+ showSizeChanger={false}
54
+ total={agentGroups.length}
55
+ />
56
+ </Flexbox>
57
+ )}
58
+ </Flexbox>
59
+ );
60
+ },
61
+ );
62
+
63
+ export default UserForkedAgentGroups;
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import { Flexbox, Grid, Tag, Text } from '@lobehub/ui';
4
+ import { Pagination } from 'antd';
5
+ import { GitForkIcon } from 'lucide-react';
6
+ import { memo, useMemo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { type DiscoverAssistantItem } from '@/types/discover';
10
+
11
+ import UserAgentCard from './UserAgentCard';
12
+
13
+ interface UserForkedAgentsProps {
14
+ agents?: DiscoverAssistantItem[];
15
+ pageSize?: number;
16
+ rows?: number;
17
+ }
18
+
19
+ const UserForkedAgents = memo<UserForkedAgentsProps>(({ agents = [], rows = 4, pageSize = 10 }) => {
20
+ const { t } = useTranslation('discover');
21
+ const [currentPage, setCurrentPage] = useState(1);
22
+
23
+ const paginatedAgents = useMemo(() => {
24
+ const startIndex = (currentPage - 1) * pageSize;
25
+ return agents.slice(startIndex, startIndex + pageSize);
26
+ }, [agents, currentPage, pageSize]);
27
+
28
+ if (agents.length === 0) return null;
29
+
30
+ const showPagination = agents.length > pageSize;
31
+
32
+ return (
33
+ <Flexbox gap={16}>
34
+ <Flexbox align={'center'} gap={8} horizontal>
35
+ <GitForkIcon size={16} />
36
+ <Text fontSize={16} weight={500}>
37
+ {t('user.forkedAgents')}
38
+ </Text>
39
+ <Tag>{agents.length}</Tag>
40
+ </Flexbox>
41
+ <Grid rows={rows} width={'100%'}>
42
+ {paginatedAgents.map((agent, index) => (
43
+ <UserAgentCard key={agent.identifier || index} {...agent} />
44
+ ))}
45
+ </Grid>
46
+ {showPagination && (
47
+ <Flexbox align={'center'} justify={'center'}>
48
+ <Pagination
49
+ current={currentPage}
50
+ onChange={(page) => setCurrentPage(page)}
51
+ pageSize={pageSize}
52
+ showSizeChanger={false}
53
+ total={agents.length}
54
+ />
55
+ </Flexbox>
56
+ )}
57
+ </Flexbox>
58
+ );
59
+ });
60
+
61
+ export default UserForkedAgents;
@@ -61,12 +61,14 @@ const UserDetailPage = memo<UserDetailPageProps>(({ mobile }) => {
61
61
 
62
62
  const contextConfig = useMemo(() => {
63
63
  if (!data || !data.user) return null;
64
- const { user, agents, agentGroups } = data;
64
+ const { user, agents, agentGroups, forkedAgents, forkedAgentGroups } = data;
65
65
  const totalInstalls = agents.reduce((sum, agent) => sum + (agent.installCount || 0), 0);
66
66
  return {
67
67
  agentCount: agents.length,
68
68
  agentGroups: agentGroups || [],
69
69
  agents,
70
+ forkedAgentGroups: forkedAgentGroups || [],
71
+ forkedAgents: forkedAgents || [],
70
72
  groupCount: agentGroups?.length || 0,
71
73
  isOwner,
72
74
  mobile,
@@ -3,7 +3,6 @@ import { cssVar } from 'antd-style';
3
3
  import { MessageSquareDashed, Star } from 'lucide-react';
4
4
  import { Suspense, memo, useCallback, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- import urlJoin from 'url-join';
7
6
 
8
7
  import { isDesktop } from '@/const/version';
9
8
  import NavItem from '@/features/NavPanel/components/NavItem';
@@ -35,7 +34,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
35
34
  // Construct href for cmd+click support
36
35
  const href = useMemo(() => {
37
36
  if (!activeGroupId || !id) return undefined;
38
- return urlJoin('/group', activeGroupId, `?topic=${id}`);
37
+ return `/group/${activeGroupId}?topic=${id}`;
39
38
  }, [activeGroupId, id]);
40
39
 
41
40
  const [editing, isLoading] = useChatStore((s) => [
@@ -7,7 +7,8 @@ import { memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  const WelcomeText = memo(() => {
10
- const { t } = useTranslation('welcome');
10
+ const { t, i18n } = useTranslation('welcome');
11
+ const locale = i18n.language;
11
12
 
12
13
  const sentences = useMemo(() => {
13
14
  const messages = t('welcomeMessages', { returnObjects: true }) as Record<string, string>;
@@ -28,6 +29,7 @@ const WelcomeText = memo(() => {
28
29
  deletePauseDuration={1000}
29
30
  deletingSpeed={32}
30
31
  hideCursorWhileTyping={'afterTyping'}
32
+ key={locale}
31
33
  pauseDuration={16_000}
32
34
  sentences={sentences}
33
35
  typingSpeed={64}
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { isDesktop } from '@lobechat/const';
4
- import { FormGroup, Skeleton, Text } from '@lobehub/ui';
5
- import { Divider } from 'antd';
4
+ import { Flexbox, FormGroup, Text } from '@lobehub/ui';
5
+ import { Skeleton as AntSkeleton, Divider } from 'antd';
6
6
  import { useEffect } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
@@ -19,10 +19,33 @@ import FullNameRow from './features/FullNameRow';
19
19
  import InterestsRow from './features/InterestsRow';
20
20
  import KlavisAuthorizationList from './features/KlavisAuthorizationList';
21
21
  import PasswordRow from './features/PasswordRow';
22
- import ProfileRow from './features/ProfileRow';
22
+ import ProfileRow, { labelStyle, rowStyle } from './features/ProfileRow';
23
23
  import SSOProvidersList from './features/SSOProvidersList';
24
24
  import UsernameRow from './features/UsernameRow';
25
25
 
26
+ const SkeletonRow = ({ mobile }: { mobile?: boolean }) => {
27
+ if (mobile) {
28
+ return (
29
+ <Flexbox gap={12} style={rowStyle}>
30
+ <Flexbox align="center" horizontal justify="space-between">
31
+ <AntSkeleton.Input active size="small" style={{ height: 22, width: 60 }} />
32
+ <AntSkeleton.Input active size="small" style={{ height: 22, width: 80 }} />
33
+ </Flexbox>
34
+ <AntSkeleton.Input active size="small" style={{ height: 22, width: 120 }} />
35
+ </Flexbox>
36
+ );
37
+ }
38
+ return (
39
+ <Flexbox align="center" gap={24} horizontal justify="space-between" style={rowStyle}>
40
+ <Flexbox align="center" gap={24} horizontal style={{ flex: 1 }}>
41
+ <AntSkeleton.Input active size="small" style={{ ...labelStyle, height: 22 }} />
42
+ <AntSkeleton.Input active size="small" style={{ height: 22, minWidth: 120, width: 160 }} />
43
+ </Flexbox>
44
+ <AntSkeleton.Input active size="small" style={{ height: 22, width: 100 }} />
45
+ </Flexbox>
46
+ );
47
+ };
48
+
26
49
  interface ProfileSettingProps {
27
50
  mobile?: boolean;
28
51
  }
@@ -63,75 +86,76 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
63
86
 
64
87
  const { t } = useTranslation('auth');
65
88
 
66
- if (isLoading)
67
- return (
68
- <Skeleton
69
- active
70
- paragraph={{ rows: 6 }}
71
- style={{ padding: mobile ? 16 : undefined }}
72
- title={false}
73
- />
74
- );
75
-
76
89
  return (
77
90
  <>
78
91
  <SettingHeader title={t('profile.title')} />
79
92
  <FormGroup collapsible={false} gap={16} title={t('profile.account')} variant={'filled'}>
80
- {/* Avatar Row - Editable */}
81
- <AvatarRow mobile={mobile} />
82
-
83
- <Divider style={{ margin: 0 }} />
84
-
85
- {/* Full Name Row - Editable */}
86
- <FullNameRow mobile={mobile} />
87
-
88
- <Divider style={{ margin: 0 }} />
89
-
90
- {/* Username Row - Editable */}
91
- <UsernameRow mobile={mobile} />
92
-
93
- <Divider style={{ margin: 0 }} />
94
-
95
- {/* Interests Row - Editable */}
96
- <InterestsRow mobile={mobile} />
97
-
98
- {/* Password Row - For Better Auth users to change or set password */}
99
- {!isDesktop && isLoginWithBetterAuth && (
100
- <>
101
- <Divider style={{ margin: 0 }} />
102
- <PasswordRow mobile={mobile} />
103
- </>
104
- )}
105
-
106
- {/* Email Row - Read Only */}
107
- {isLoginWithAuth && userProfile?.email && (
108
- <>
109
- <Divider style={{ margin: 0 }} />
110
- <ProfileRow label={t('profile.email')} mobile={mobile}>
111
- <Text>{userProfile.email}</Text>
112
- </ProfileRow>
113
- </>
114
- )}
115
-
116
- {/* SSO Providers Row */}
117
- {isLoginWithAuth && (
118
- <>
119
- <Divider style={{ margin: 0 }} />
120
- <ProfileRow label={t('profile.sso.providers')} mobile={mobile}>
121
- <SSOProvidersList />
122
- </ProfileRow>
123
- </>
124
- )}
125
-
126
- {/* Klavis Authorizations Row */}
127
- {enableKlavis && connectedServers.length > 0 && (
128
- <>
129
- <Divider style={{ margin: 0 }} />
130
- <ProfileRow label={t('profile.authorizations.title')} mobile={mobile}>
131
- <KlavisAuthorizationList servers={connectedServers} />
132
- </ProfileRow>
133
- </>
134
- )}
93
+ <Flexbox style={{ display: isLoading ? 'flex' : 'none' }}>
94
+ <SkeletonRow mobile={mobile} />
95
+ <Divider style={{ margin: 0 }} />
96
+ <SkeletonRow mobile={mobile} />
97
+ <Divider style={{ margin: 0 }} />
98
+ <SkeletonRow mobile={mobile} />
99
+ <Divider style={{ margin: 0 }} />
100
+ <SkeletonRow mobile={mobile} />
101
+ </Flexbox>
102
+ <Flexbox style={{ display: isLoading ? 'none' : 'flex' }}>
103
+ {/* Avatar Row - Editable */}
104
+ <AvatarRow mobile={mobile} />
105
+
106
+ <Divider style={{ margin: 0 }} />
107
+
108
+ {/* Full Name Row - Editable */}
109
+ <FullNameRow mobile={mobile} />
110
+
111
+ <Divider style={{ margin: 0 }} />
112
+
113
+ {/* Username Row - Editable */}
114
+ <UsernameRow mobile={mobile} />
115
+
116
+ <Divider style={{ margin: 0 }} />
117
+
118
+ {/* Interests Row - Editable */}
119
+ <InterestsRow mobile={mobile} />
120
+
121
+ {/* Password Row - For Better Auth users to change or set password */}
122
+ {!isDesktop && isLoginWithBetterAuth && (
123
+ <>
124
+ <Divider style={{ margin: 0 }} />
125
+ <PasswordRow mobile={mobile} />
126
+ </>
127
+ )}
128
+
129
+ {/* Email Row - Read Only */}
130
+ {isLoginWithAuth && userProfile?.email && (
131
+ <>
132
+ <Divider style={{ margin: 0 }} />
133
+ <ProfileRow label={t('profile.email')} mobile={mobile}>
134
+ <Text>{userProfile.email}</Text>
135
+ </ProfileRow>
136
+ </>
137
+ )}
138
+
139
+ {/* SSO Providers Row */}
140
+ {isLoginWithAuth && (
141
+ <>
142
+ <Divider style={{ margin: 0 }} />
143
+ <ProfileRow label={t('profile.sso.providers')} mobile={mobile}>
144
+ <SSOProvidersList />
145
+ </ProfileRow>
146
+ </>
147
+ )}
148
+
149
+ {/* Klavis Authorizations Row */}
150
+ {enableKlavis && connectedServers.length > 0 && (
151
+ <>
152
+ <Divider style={{ margin: 0 }} />
153
+ <ProfileRow label={t('profile.authorizations.title')} mobile={mobile}>
154
+ <KlavisAuthorizationList servers={connectedServers} />
155
+ </ProfileRow>
156
+ </>
157
+ )}
158
+ </Flexbox>
135
159
  </FormGroup>
136
160
  </>
137
161
  );
@@ -1,5 +1,6 @@
1
1
  import { Flexbox } from '@lobehub/ui';
2
2
 
3
+ import TopicListContent from '@/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent';
3
4
  import TopicSearchBar from '@/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicSearchBar';
4
5
 
5
6
  import TopicModal from './features/TopicModal';
@@ -14,7 +15,7 @@ const Topic = () => {
14
15
  style={{ marginInline: -8, overflow: 'hidden', position: 'relative' }}
15
16
  width={'calc(100% + 16px)'}
16
17
  >
17
- <Topic />
18
+ <TopicListContent />
18
19
  </Flexbox>
19
20
  </Flexbox>
20
21
  </TopicModal>
@@ -2,6 +2,7 @@ import { Flexbox, type FlexboxProps, Text } from '@lobehub/ui';
2
2
  import { TypewriterEffect, type TypewriterEffectProps } from '@lobehub/ui/awesome';
3
3
  import { LoadingDots } from '@lobehub/ui/chat';
4
4
  import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
5
6
 
6
7
  import { ProductLogo } from '@/components/Branding';
7
8
 
@@ -11,6 +12,9 @@ interface LobeMessageProps extends Omit<FlexboxProps, 'children'> {
11
12
  }
12
13
 
13
14
  const LobeMessage = memo<LobeMessageProps>(({ sentences, fontSize = 24, ...rest }) => {
15
+ const { i18n } = useTranslation();
16
+ const locale = i18n.language;
17
+
14
18
  return (
15
19
  <Flexbox gap={8} {...rest}>
16
20
  <ProductLogo size={fontSize * 2} />
@@ -21,6 +25,7 @@ const LobeMessage = memo<LobeMessageProps>(({ sentences, fontSize = 24, ...rest
21
25
  deletePauseDuration={1000}
22
26
  deletingSpeed={32}
23
27
  hideCursorWhileTyping={'afterTyping'}
28
+ key={locale}
24
29
  pauseDuration={16_000}
25
30
  sentences={sentences}
26
31
  typingSpeed={64}
@@ -19,7 +19,8 @@ interface TelemetryStepProps {
19
19
  }
20
20
 
21
21
  const TelemetryStep = memo<TelemetryStepProps>(({ onNext }) => {
22
- const { t } = useTranslation('onboarding');
22
+ const { t, i18n } = useTranslation('onboarding');
23
+ const locale = i18n.language;
23
24
  const [check, setCheck] = useState(true);
24
25
  const [isNavigating, setIsNavigating] = useState(false);
25
26
  const isNavigatingRef = useRef(false);
@@ -63,6 +64,7 @@ const TelemetryStep = memo<TelemetryStepProps>(({ onNext }) => {
63
64
  deletePauseDuration={1000}
64
65
  deletingSpeed={32}
65
66
  hideCursorWhileTyping={'afterTyping'}
67
+ key={locale}
66
68
  pauseDuration={16_000}
67
69
  sentences={[
68
70
  t('telemetry.title', { name: 'Lobe AI' }),
@@ -0,0 +1,105 @@
1
+ import { DEFAULT_AVATAR, DEFAULT_INBOX_AVATAR } from '@lobechat/const';
2
+ import { Avatar } from '@lobehub/ui';
3
+ import { Command } from 'cmdk';
4
+ import { memo, useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useAgentStore } from '@/store/agent';
8
+ import { builtinAgentSelectors } from '@/store/agent/selectors/builtinAgentSelectors';
9
+ import { useHomeStore } from '@/store/home';
10
+ import { homeAgentListSelectors } from '@/store/home/selectors';
11
+
12
+ import { useCommandMenuContext } from './CommandMenuContext';
13
+ import { styles } from './styles';
14
+
15
+ const AskAgentCommands = memo(() => {
16
+ const { t } = useTranslation('common');
17
+ const { search, setSearch, setSelectedAgent } = useCommandMenuContext();
18
+
19
+ const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
20
+ const allAgents = useHomeStore(homeAgentListSelectors.allAgents);
21
+
22
+ // Check if search starts with "@"
23
+ const isAtMention = search.trimStart().startsWith('@');
24
+
25
+ // Get the query after "@" for filtering
26
+ const mentionQuery = useMemo(() => {
27
+ if (!isAtMention) return '';
28
+ return search.trimStart().slice(1).toLowerCase();
29
+ }, [search, isAtMention]);
30
+
31
+ // Filter agents based on the query after "@"
32
+ const filteredAgents = useMemo(() => {
33
+ const agents = allAgents.filter((item) => item.type === 'agent');
34
+ if (!mentionQuery) {
35
+ return agents.slice(0, 10);
36
+ }
37
+ return agents
38
+ .filter((agent) => {
39
+ const title = (agent.title || '').toLowerCase();
40
+ return title.includes(mentionQuery);
41
+ })
42
+ .slice(0, 10);
43
+ }, [allAgents, mentionQuery]);
44
+
45
+ const handleAgentSelect = (agentId: string, agentTitle: string, agentAvatar: string) => {
46
+ setSelectedAgent({
47
+ avatar: agentAvatar,
48
+ id: agentId,
49
+ title: agentTitle,
50
+ });
51
+ setSearch('');
52
+ };
53
+
54
+ // Only show when user types "@"
55
+ if (!isAtMention) return null;
56
+
57
+ // Check if Lobe AI matches the query
58
+ const showLobeAI = !mentionQuery || 'lobe ai'.includes(mentionQuery);
59
+
60
+ return (
61
+ <Command.Group heading={t('cmdk.mentionAgent')}>
62
+ {/* @Lobe AI option */}
63
+ {showLobeAI && (
64
+ <Command.Item
65
+ onSelect={() => handleAgentSelect(inboxAgentId, 'Lobe AI', DEFAULT_INBOX_AVATAR)}
66
+ value="@lobe-ai"
67
+ >
68
+ <Avatar avatar={DEFAULT_INBOX_AVATAR} emojiScaleWithBackground shape="square" size={18} />
69
+ <div className={styles.itemContent}>
70
+ <div className={styles.itemLabel}>@Lobe AI</div>
71
+ </div>
72
+ </Command.Item>
73
+ )}
74
+
75
+ {/* @agent options */}
76
+ {filteredAgents.map((agent) => (
77
+ <Command.Item
78
+ key={agent.id}
79
+ onSelect={() =>
80
+ handleAgentSelect(
81
+ agent.id,
82
+ agent.title || t('defaultAgent'),
83
+ typeof agent.avatar === 'string' ? agent.avatar : DEFAULT_AVATAR,
84
+ )
85
+ }
86
+ value={`@${agent.title || 'agent'}-${agent.id}`}
87
+ >
88
+ <Avatar
89
+ avatar={typeof agent.avatar === 'string' ? agent.avatar : DEFAULT_AVATAR}
90
+ emojiScaleWithBackground
91
+ shape="square"
92
+ size={18}
93
+ />
94
+ <div className={styles.itemContent}>
95
+ <div className={styles.itemLabel}>@{agent.title || t('defaultAgent')}</div>
96
+ </div>
97
+ </Command.Item>
98
+ ))}
99
+ </Command.Group>
100
+ );
101
+ });
102
+
103
+ AskAgentCommands.displayName = 'AskAgentCommands';
104
+
105
+ export default AskAgentCommands;
@@ -5,12 +5,13 @@ import {
5
5
  type ReactNode,
6
6
  type SetStateAction,
7
7
  createContext,
8
+ useCallback,
8
9
  useContext,
9
- useEffect,
10
+ useMemo,
10
11
  useState,
11
12
  } from 'react';
12
13
 
13
- import { type MenuContext, type PageType } from './types';
14
+ import { type MenuContext, type PageType, type SelectedAgent } from './types';
14
15
  import { detectContext } from './utils/context';
15
16
  import type { ValidSearchType } from './utils/queryParser';
16
17
 
@@ -21,8 +22,10 @@ interface CommandMenuContextValue {
21
22
  pages: PageType[];
22
23
  pathname: string | null;
23
24
  search: string;
25
+ selectedAgent: SelectedAgent | undefined;
24
26
  setPages: Dispatch<SetStateAction<PageType[]>>;
25
27
  setSearch: (search: string) => void;
28
+ setSelectedAgent: (agent: SelectedAgent | undefined) => void;
26
29
  setTypeFilter: (typeFilter: ValidSearchType | undefined) => void;
27
30
  setViewMode: (viewMode: MenuViewMode) => void;
28
31
  typeFilter: ValidSearchType | undefined;
@@ -39,49 +42,65 @@ interface CommandMenuProviderProps {
39
42
  }
40
43
 
41
44
  export const CommandMenuProvider = ({ children, pathname }: CommandMenuProviderProps) => {
42
- const menuContext = detectContext(pathname ?? '/');
43
- const [viewMode, setViewMode] = useState<MenuViewMode>('default');
44
45
  const [pages, setPages] = useState<PageType[]>([]);
45
- const [search, setSearch] = useState('');
46
- const [typeFilter, setTypeFilter] = useState<ValidSearchType | undefined>(undefined);
47
- const [mounted, setMounted] = useState(false);
46
+ const [search, setSearchState] = useState('');
47
+ const [typeFilter, setTypeFilterState] = useState<ValidSearchType | undefined>(undefined);
48
+ const [selectedAgent, setSelectedAgentState] = useState<SelectedAgent | undefined>(undefined);
48
49
 
49
- // Derived values
50
+ // Memoize derived values
51
+ const menuContext = useMemo(() => detectContext(pathname ?? '/'), [pathname]);
50
52
  const page = pages.at(-1);
53
+ const viewMode: MenuViewMode = search.trim().length > 0 ? 'search' : 'default';
51
54
 
52
- // Ensure we're mounted on the client
53
- useEffect(() => {
54
- setMounted(true);
55
+ // Memoize setters to maintain stable references
56
+ const setSearch = useCallback((value: string) => setSearchState(value), []);
57
+ const setTypeFilter = useCallback(
58
+ (value: ValidSearchType | undefined) => setTypeFilterState(value),
59
+ [],
60
+ );
61
+ const setSelectedAgent = useCallback(
62
+ (value: SelectedAgent | undefined) => setSelectedAgentState(value),
63
+ [],
64
+ );
65
+ const setViewMode = useCallback(() => {
66
+ // viewMode is now derived from search, this is a no-op for backwards compatibility
55
67
  }, []);
56
68
 
57
- useEffect(() => {
58
- if (search.trim().length > 0) {
59
- setViewMode('search');
60
- } else {
61
- setViewMode('default');
62
- }
63
- }, [search]);
64
-
65
- return (
66
- <CommandMenuContext.Provider
67
- value={{
68
- menuContext,
69
- mounted,
70
- page,
71
- pages,
72
- pathname,
73
- search,
74
- setPages,
75
- setSearch,
76
- setTypeFilter,
77
- setViewMode,
78
- typeFilter,
79
- viewMode,
80
- }}
81
- >
82
- {children}
83
- </CommandMenuContext.Provider>
69
+ // Memoize the context value to prevent unnecessary re-renders
70
+ const contextValue = useMemo<CommandMenuContextValue>(
71
+ () => ({
72
+ menuContext,
73
+ mounted: true, // Always true after initial render since provider only mounts on client
74
+ page,
75
+ pages,
76
+ pathname,
77
+ search,
78
+ selectedAgent,
79
+ setPages,
80
+ setSearch,
81
+ setSelectedAgent,
82
+ setTypeFilter,
83
+ setViewMode,
84
+ typeFilter,
85
+ viewMode,
86
+ }),
87
+ [
88
+ menuContext,
89
+ page,
90
+ pages,
91
+ pathname,
92
+ search,
93
+ selectedAgent,
94
+ setSearch,
95
+ setSelectedAgent,
96
+ setTypeFilter,
97
+ setViewMode,
98
+ typeFilter,
99
+ viewMode,
100
+ ],
84
101
  );
102
+
103
+ return <CommandMenuContext.Provider value={contextValue}>{children}</CommandMenuContext.Provider>;
85
104
  };
86
105
 
87
106
  export const useCommandMenuContext = () => {