@lobehub/lobehub 2.0.0-next.280 → 2.0.0-next.282

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 (42) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/e2e/src/steps/agent/conversation-mgmt.steps.ts +47 -3
  4. package/package.json +1 -1
  5. package/packages/builtin-tool-group-agent-builder/src/client/Render/BatchCreateAgents.tsx +19 -3
  6. package/packages/builtin-tool-group-agent-builder/src/client/Streaming/BatchCreateAgents/index.tsx +19 -4
  7. package/packages/builtin-tool-group-agent-builder/src/client/Streaming/UpdateAgentPrompt/index.tsx +3 -13
  8. package/packages/builtin-tool-group-agent-builder/src/client/Streaming/UpdateGroupPrompt/index.tsx +1 -1
  9. package/packages/builtin-tool-group-agent-builder/src/systemRole.ts +83 -121
  10. package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +20 -2
  11. package/packages/observability-otel/src/node.ts +40 -3
  12. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +9 -1
  13. package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +1 -28
  14. package/src/app/[variants]/(main)/community/(detail)/features/MakedownRender.tsx +3 -3
  15. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +8 -1
  16. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/Header/Avatar.tsx +2 -13
  17. package/src/app/[variants]/(main)/group/_layout/Sidebar/Header/Agent/index.tsx +3 -4
  18. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +17 -8
  19. package/src/app/[variants]/(main)/group/features/Conversation/ConversationArea.tsx +1 -29
  20. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +0 -2
  21. package/src/app/[variants]/(main)/group/features/GroupAvatar.tsx +17 -9
  22. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/TopicSelector.tsx +8 -5
  23. package/src/app/[variants]/(main)/group/profile/features/Header/ChromeTabs/index.tsx +20 -2
  24. package/src/app/[variants]/(main)/group/profile/features/MemberProfile/AgentTool.tsx +4 -2
  25. package/src/app/[variants]/(main)/group/profile/features/ProfileHydration.tsx +5 -25
  26. package/src/features/AgentGroupAvatar/index.tsx +38 -0
  27. package/src/features/Conversation/Messages/Supervisor/index.tsx +8 -2
  28. package/src/features/Conversation/Messages/User/useMarkdown.tsx +1 -2
  29. package/src/features/EditorModal/EditorCanvas.tsx +62 -0
  30. package/src/features/EditorModal/TextArea.tsx +30 -0
  31. package/src/features/EditorModal/Typobar.tsx +139 -0
  32. package/src/features/EditorModal/index.tsx +18 -8
  33. package/src/features/NavPanel/components/EmptyNavItem.tsx +2 -2
  34. package/src/features/NavPanel/components/NavItem.tsx +27 -3
  35. package/src/features/ToolTag/index.tsx +167 -0
  36. package/src/server/routers/lambda/topic.ts +8 -1
  37. package/src/services/chat/mecha/contextEngineering.test.ts +1 -1
  38. package/src/services/chat/mecha/contextEngineering.ts +3 -4
  39. package/src/services/chat/mecha/memoryManager.ts +9 -38
  40. package/src/store/agentGroup/initialState.ts +1 -1
  41. package/src/store/agentGroup/slices/lifecycle.ts +15 -2
  42. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +0 -49
@@ -1,8 +1,9 @@
1
1
  import { ActionIcon, Flexbox, Icon, Skeleton, Tag } from '@lobehub/ui';
2
2
  import { cssVar } from 'antd-style';
3
3
  import { MessageSquareDashed, Star } from 'lucide-react';
4
- import { Suspense, memo, useCallback } from 'react';
4
+ import { Suspense, memo, useCallback, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
+ import urlJoin from 'url-join';
6
7
 
7
8
  import { isDesktop } from '@/const/version';
8
9
  import NavItem from '@/features/NavPanel/components/NavItem';
@@ -29,6 +30,12 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
29
30
  const openTopicInNewWindow = useGlobalStore((s) => s.openTopicInNewWindow);
30
31
  const activeAgentId = useAgentStore((s) => s.activeAgentId);
31
32
 
33
+ // Construct href for cmd+click support
34
+ const href = useMemo(() => {
35
+ if (!activeAgentId || !id) return undefined;
36
+ return urlJoin('/chat', `?agent=${activeAgentId}&topic=${id}`);
37
+ }, [activeAgentId, id]);
38
+
32
39
  const [editing, isLoading] = useChatStore((s) => [
33
40
  id ? s.topicRenamingId === id : false,
34
41
  id ? s.topicLoadingIds.includes(id) : false,
@@ -97,6 +104,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
97
104
  active={active && !threadId && !isInAgentSubRoute}
98
105
  contextMenuItems={dropdownMenu}
99
106
  disabled={editing}
107
+ href={href}
100
108
  icon={
101
109
  <ActionIcon
102
110
  color={fav ? cssVar.colorWarning : undefined}
@@ -1,20 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import { Flexbox } from '@lobehub/ui';
4
- import { Suspense, memo, useEffect, useMemo } from 'react';
4
+ import { Suspense, memo, useMemo } from 'react';
5
5
 
6
6
  import ChatMiniMap from '@/features/ChatMiniMap';
7
7
  import { ChatList, ConversationProvider, TodoProgress } from '@/features/Conversation';
8
8
  import ZenModeToast from '@/features/ZenModeToast';
9
9
  import { useOperationState } from '@/hooks/useOperationState';
10
- import { useAgentStore } from '@/store/agent';
11
- import { agentSelectors } from '@/store/agent/selectors';
12
10
  import { useChatStore } from '@/store/chat';
13
- import { topicSelectors } from '@/store/chat/selectors';
14
11
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
15
- import { useUserStore } from '@/store/user';
16
- import { settingsSelectors } from '@/store/user/selectors';
17
- import { useUserMemoryStore } from '@/store/userMemory';
18
12
 
19
13
  import WelcomeChatItem from './AgentWelcome';
20
14
  import ChatHydration from './ChatHydration';
@@ -33,27 +27,6 @@ import { useAgentContext } from './useAgentContext';
33
27
  const Conversation = memo(() => {
34
28
  const context = useAgentContext();
35
29
 
36
- const [useFetchUserMemory, setActiveMemoryContext] = useUserMemoryStore((s) => [
37
- s.useFetchUserMemory,
38
- s.setActiveMemoryContext,
39
- ]);
40
- const [currentAgentMeta, activeTopic] = [
41
- useAgentStore(agentSelectors.currentAgentMeta),
42
- useChatStore(topicSelectors.currentActiveTopic),
43
- ];
44
- const enableUserMemories = useUserStore(settingsSelectors.memoryEnabled);
45
-
46
- useEffect(() => {
47
- if (!enableUserMemories) {
48
- setActiveMemoryContext(undefined);
49
- return;
50
- }
51
-
52
- setActiveMemoryContext({ agent: currentAgentMeta, topic: activeTopic });
53
- }, [activeTopic, currentAgentMeta, enableUserMemories, setActiveMemoryContext]);
54
-
55
- useFetchUserMemory(Boolean(enableUserMemories && context.agentId));
56
-
57
30
  // Get raw dbMessages from ChatStore for this context
58
31
  // ConversationStore will parse them internally to generate displayMessages
59
32
  const chatKey = useMemo(
@@ -3,7 +3,7 @@
3
3
  import { Center, Empty, Markdown } from '@lobehub/ui';
4
4
  import { FileText } from 'lucide-react';
5
5
  import Link from 'next/link';
6
- import { memo } from 'react';
6
+ import { type ReactNode, memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { H1, H2, H3, H4, H5 } from './Toc/Heading';
@@ -26,7 +26,7 @@ const MarkdownRender = memo<{ children?: string }>(({ children }) => {
26
26
  <Markdown
27
27
  allowHtml
28
28
  components={{
29
- a: ({ href, ...rest }) => {
29
+ a: ({ href, ...rest }: { children?: ReactNode; href?: string }) => {
30
30
  if (href && href.startsWith('http'))
31
31
  return <Link {...rest} href={href} target={'_blank'} />;
32
32
  return rest?.children;
@@ -36,7 +36,7 @@ const MarkdownRender = memo<{ children?: string }>(({ children }) => {
36
36
  h3: H3,
37
37
  h4: H4,
38
38
  h5: H5,
39
- img: ({ src, ...rest }) => {
39
+ img: ({ src, ...rest }: { alt?: string; src?: string | Blob }) => {
40
40
  // FIXME ignore experimental blob image prop passing
41
41
  if (typeof src !== 'string') return null;
42
42
  if (src.includes('glama.ai')) return null;
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
8
8
  import { DEFAULT_AVATAR } from '@/const/meta';
9
9
  import NavItem from '@/features/NavPanel/components/NavItem';
10
10
  import UserAvatar from '@/features/User/UserAvatar';
11
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
11
12
  import { useAgentGroupStore } from '@/store/agentGroup';
12
13
  import { agentGroupSelectors } from '@/store/agentGroup/selectors';
13
14
  import { useChatStore } from '@/store/chat';
@@ -30,6 +31,7 @@ interface GroupMemberProps {
30
31
  */
31
32
  const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange, groupId }) => {
32
33
  const { t } = useTranslation('chat');
34
+ const router = useQueryRoute();
33
35
  const [nickname, username] = useUserStore((s) => [
34
36
  userProfileSelectors.nickName(s),
35
37
  userProfileSelectors.username(s),
@@ -80,6 +82,11 @@ const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange
80
82
  pushPortalView({ agentId, type: PortalViewType.GroupThread });
81
83
  };
82
84
 
85
+ const handleMemberDoubleClick = (agentId: string) => {
86
+ if (!groupId) return;
87
+ router.push(`/group/${groupId}/profile`, { query: { tab: agentId }, replace: true });
88
+ };
89
+
83
90
  return (
84
91
  <>
85
92
  <Flexbox gap={2}>
@@ -93,7 +100,7 @@ const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange
93
100
  key={item.id}
94
101
  onChat={() => handleMemberClick(item.id)}
95
102
  >
96
- <div>
103
+ <div onDoubleClick={() => handleMemberDoubleClick(item.id)}>
97
104
  <GroupMemberItem
98
105
  actions={
99
106
  <ActionIcon
@@ -3,15 +3,10 @@
3
3
  import { Block } from '@lobehub/ui';
4
4
  import { memo } from 'react';
5
5
 
6
- import GroupAvatar from '@/features/GroupAvatar';
6
+ import SupervisorAvatar from '@/app/[variants]/(main)/group/features/GroupAvatar';
7
7
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
8
- import { useAgentGroupStore } from '@/store/agentGroup';
9
- import { agentGroupSelectors } from '@/store/agentGroup/selectors';
10
8
 
11
9
  const HeaderAvatar = memo<{ size?: number }>(() => {
12
- const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup);
13
- const agents = currentGroup?.agents || [];
14
-
15
10
  const openChatSettings = useOpenChatSettings();
16
11
 
17
12
  return (
@@ -30,13 +25,7 @@ const HeaderAvatar = memo<{ size?: number }>(() => {
30
25
  variant={'borderless'}
31
26
  width={32}
32
27
  >
33
- <GroupAvatar
34
- avatars={agents.map((agent) => ({
35
- avatar: agent.avatar,
36
- background: agent.backgroundColor || undefined,
37
- }))}
38
- size={28}
39
- />
28
+ <SupervisorAvatar size={28} />
40
29
  </Block>
41
30
  );
42
31
  });
@@ -5,7 +5,7 @@ import { ChevronsUpDownIcon } from 'lucide-react';
5
5
  import React, { type PropsWithChildren, memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import GroupAvatar from '@/features/GroupAvatar';
8
+ import SupervisorAvatar from '@/app/[variants]/(main)/group/features/GroupAvatar';
9
9
  import { SkeletonItem } from '@/features/NavPanel/components/SkeletonList';
10
10
  import { useAgentGroupStore } from '@/store/agentGroup';
11
11
  import { agentGroupSelectors } from '@/store/agentGroup/selectors';
@@ -15,10 +15,9 @@ import SwitchPanel from './SwitchPanel';
15
15
  const Agent = memo<PropsWithChildren>(() => {
16
16
  const { t } = useTranslation(['chat', 'common']);
17
17
 
18
- const [isGroupsInit, groupMeta, memberAvatars] = useAgentGroupStore((s) => [
18
+ const [isGroupsInit, groupMeta] = useAgentGroupStore((s) => [
19
19
  agentGroupSelectors.isGroupsInit(s),
20
20
  agentGroupSelectors.currentGroupMeta(s),
21
- agentGroupSelectors.currentGroupMemberAvatars(s),
22
21
  ]);
23
22
 
24
23
  const displayTitle = groupMeta?.title || t('untitledGroup', { ns: 'chat' });
@@ -39,7 +38,7 @@ const Agent = memo<PropsWithChildren>(() => {
39
38
  }}
40
39
  variant={'borderless'}
41
40
  >
42
- <GroupAvatar avatars={memberAvatars} size={28} />
41
+ <SupervisorAvatar size={28} />
43
42
  <Text ellipsis weight={500}>
44
43
  {displayTitle}
45
44
  </Text>
@@ -1,17 +1,18 @@
1
1
  import { ActionIcon, Flexbox, Icon, Skeleton, Tag } from '@lobehub/ui';
2
2
  import { cssVar } from 'antd-style';
3
3
  import { MessageSquareDashed, Star } from 'lucide-react';
4
- import { Suspense, memo, useCallback } from 'react';
4
+ import { Suspense, memo, useCallback, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
+ import urlJoin from 'url-join';
6
7
 
7
8
  import { isDesktop } from '@/const/version';
8
9
  import NavItem from '@/features/NavPanel/components/NavItem';
9
10
  import { useAgentStore } from '@/store/agent';
11
+ import { useAgentGroupStore } from '@/store/agentGroup';
10
12
  import { useChatStore } from '@/store/chat';
11
13
  import { useGlobalStore } from '@/store/global';
12
14
 
13
15
  import ThreadList from '../../TopicListContent/ThreadList';
14
- import { useTopicNavigation } from '../../hooks/useTopicNavigation';
15
16
  import Actions from './Actions';
16
17
  import Editing from './Editing';
17
18
  import { useTopicItemDropdownMenu } from './useDropdownMenu';
@@ -27,7 +28,15 @@ interface TopicItemProps {
27
28
  const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) => {
28
29
  const { t } = useTranslation('topic');
29
30
  const openTopicInNewWindow = useGlobalStore((s) => s.openTopicInNewWindow);
31
+ const toggleMobileTopic = useGlobalStore((s) => s.toggleMobileTopic);
30
32
  const activeAgentId = useAgentStore((s) => s.activeAgentId);
33
+ const [activeGroupId, switchTopic] = useAgentGroupStore((s) => [s.activeGroupId, s.switchTopic]);
34
+
35
+ // Construct href for cmd+click support
36
+ const href = useMemo(() => {
37
+ if (!activeGroupId || !id) return undefined;
38
+ return urlJoin('/group', activeGroupId, `?topic=${id}`);
39
+ }, [activeGroupId, id]);
31
40
 
32
41
  const [editing, isLoading] = useChatStore((s) => [
33
42
  id ? s.topicRenamingId === id : false,
@@ -36,8 +45,6 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
36
45
 
37
46
  const [favoriteTopic] = useChatStore((s) => [s.favoriteTopic]);
38
47
 
39
- const { navigateToTopic, isInAgentSubRoute } = useTopicNavigation();
40
-
41
48
  const toggleEditing = useCallback(
42
49
  (visible?: boolean) => {
43
50
  useChatStore.setState({ topicRenamingId: visible && id ? id : '' });
@@ -47,8 +54,9 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
47
54
 
48
55
  const handleClick = useCallback(() => {
49
56
  if (editing) return;
50
- navigateToTopic(id);
51
- }, [editing, id, navigateToTopic]);
57
+ switchTopic(id);
58
+ toggleMobileTopic(false);
59
+ }, [editing, id, switchTopic, toggleMobileTopic]);
52
60
 
53
61
  const handleDoubleClick = useCallback(() => {
54
62
  if (!id || !activeAgentId) return;
@@ -66,7 +74,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
66
74
  if (!id) {
67
75
  return (
68
76
  <NavItem
69
- active={active && !isInAgentSubRoute}
77
+ active={active}
70
78
  icon={
71
79
  <Icon color={cssVar.colorTextDescription} icon={MessageSquareDashed} size={'small'} />
72
80
  }
@@ -94,9 +102,10 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
94
102
  <Flexbox style={{ position: 'relative' }}>
95
103
  <NavItem
96
104
  actions={<Actions dropdownMenu={dropdownMenu} />}
97
- active={active && !threadId && !isInAgentSubRoute}
105
+ active={active && !threadId}
98
106
  contextMenuItems={dropdownMenu}
99
107
  disabled={editing}
108
+ href={!editing ? href : undefined}
100
109
  icon={
101
110
  <ActionIcon
102
111
  color={fav ? cssVar.colorWarning : undefined}
@@ -1,20 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import { Flexbox } from '@lobehub/ui';
4
- import { Suspense, memo, useEffect, useMemo } from 'react';
4
+ import { Suspense, memo, useMemo } from 'react';
5
5
 
6
6
  import ChatMiniMap from '@/features/ChatMiniMap';
7
7
  import { ChatList, ConversationProvider } from '@/features/Conversation';
8
8
  import ZenModeToast from '@/features/ZenModeToast';
9
9
  import { useOperationState } from '@/hooks/useOperationState';
10
- import { useAgentStore } from '@/store/agent';
11
- import { agentSelectors } from '@/store/agent/selectors';
12
10
  import { useChatStore } from '@/store/chat';
13
- import { topicSelectors } from '@/store/chat/selectors';
14
11
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
15
- import { useUserStore } from '@/store/user';
16
- import { settingsSelectors } from '@/store/user/selectors';
17
- import { useUserMemoryStore } from '@/store/userMemory';
18
12
 
19
13
  import WelcomeChatItem from './AgentWelcome';
20
14
  import ChatHydration from './ChatHydration';
@@ -39,28 +33,6 @@ interface ConversationAreaProps {
39
33
  const Conversation = memo<ConversationAreaProps>(({ mobile = false }) => {
40
34
  const context = useGroupContext();
41
35
 
42
- const [useFetchUserMemory, setActiveMemoryContext] = useUserMemoryStore((s) => [
43
- s.useFetchUserMemory,
44
- s.setActiveMemoryContext,
45
- ]);
46
- const [currentAgentMeta, activeTopic] = [
47
- useAgentStore(agentSelectors.currentAgentMeta),
48
- useChatStore(topicSelectors.currentActiveTopic),
49
- ];
50
-
51
- const enableUserMemories = useUserStore(settingsSelectors.memoryEnabled);
52
-
53
- useEffect(() => {
54
- if (!enableUserMemories) {
55
- setActiveMemoryContext(undefined);
56
- return;
57
- }
58
-
59
- setActiveMemoryContext({ agent: currentAgentMeta, topic: activeTopic });
60
- }, [activeTopic, currentAgentMeta, enableUserMemories, setActiveMemoryContext]);
61
-
62
- useFetchUserMemory(Boolean(enableUserMemories && context.agentId));
63
-
64
36
  // Get raw dbMessages from ChatStore for this context
65
37
  // ConversationStore will parse them internally to generate displayMessages
66
38
  const chatKey = useMemo(
@@ -11,8 +11,6 @@ import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layou
11
11
  import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
12
12
  import { useChatStore } from '@/store/chat';
13
13
 
14
- console.log('ENABLE_TOPIC_LINK_SHARE', ENABLE_TOPIC_LINK_SHARE);
15
-
16
14
  const ShareModal = dynamic(() => import('@/features/ShareModal'));
17
15
  const SharePopover = dynamic(() => import('@/features/SharePopover'));
18
16
 
@@ -1,19 +1,27 @@
1
1
  'use client';
2
2
 
3
3
  import isEqual from 'fast-deep-equal';
4
- import React, { memo } from 'react';
4
+ import { memo } from 'react';
5
5
 
6
- import GroupAvatar from '@/features/GroupAvatar';
6
+ import AgentGroupAvatar from '@/features/AgentGroupAvatar';
7
7
  import { useAgentGroupStore } from '@/store/agentGroup';
8
8
  import { agentGroupSelectors } from '@/store/agentGroup/selectors';
9
9
 
10
- const SupervisorAvatar = memo<{ size?: number }>(({ size = 28 }) => {
11
- const memberAvatars = useAgentGroupStore(
12
- (s) => agentGroupSelectors.currentGroupMemberAvatars(s),
13
- isEqual,
14
- );
10
+ /**
11
+ * Connected AgentGroupAvatar that reads from agentGroup store
12
+ */
13
+ const CurrentAgentGroupAvatar = memo<{ size?: number }>(({ size = 28 }) => {
14
+ const groupMeta = useAgentGroupStore(agentGroupSelectors.currentGroupMeta, isEqual);
15
+ const memberAvatars = useAgentGroupStore(agentGroupSelectors.currentGroupMemberAvatars, isEqual);
15
16
 
16
- return <GroupAvatar avatars={memberAvatars} size={size} />;
17
+ return (
18
+ <AgentGroupAvatar
19
+ avatar={groupMeta.avatar}
20
+ backgroundColor={groupMeta.backgroundColor}
21
+ memberAvatars={memberAvatars}
22
+ size={size}
23
+ />
24
+ );
17
25
  });
18
26
 
19
- export default SupervisorAvatar;
27
+ export default CurrentAgentGroupAvatar;
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
7
7
  import NavHeader from '@/features/NavHeader';
8
+ import { useQueryState } from '@/hooks/useQueryParam';
8
9
  import { useChatStore } from '@/store/chat';
9
10
  import { topicSelectors } from '@/store/chat/slices/topic/selectors';
10
11
 
@@ -18,16 +19,18 @@ const TopicSelector = memo<TopicSelectorProps>(({ agentId }) => {
18
19
  // Fetch topics for the group agent builder
19
20
  useChatStore((s) => s.useFetchTopics)(true, { agentId });
20
21
 
21
- // Use activeTopicId from chatStore (synced with URL query 'bt' via ProfileHydration)
22
- const [activeTopicId, switchTopic] = useChatStore((s) => [s.activeTopicId, s.switchTopic]);
22
+ // Use activeTopicId from chatStore (synced from URL query 'bt' via ProfileHydration)
23
+ const activeTopicId = useChatStore((s) => s.activeTopicId);
23
24
  const topics = useChatStore((s) => topicSelectors.getTopicsByAgentId(agentId)(s));
24
25
 
25
- // Switch topic - ProfileHydration handles URL sync automatically
26
+ // Directly update URL query 'bt' to switch topic in profile page
27
+ const [, setBuilderTopicId] = useQueryState('bt');
28
+
26
29
  const handleSwitchTopic = useCallback(
27
30
  (topicId?: string) => {
28
- switchTopic(topicId);
31
+ setBuilderTopicId(topicId ?? null);
29
32
  },
30
- [switchTopic],
33
+ [setBuilderTopicId],
31
34
  );
32
35
 
33
36
  // Find active topic from the agent's topics list directly
@@ -3,7 +3,7 @@
3
3
  import { Avatar, Flexbox } from '@lobehub/ui';
4
4
  import { createStaticStyles, cx } from 'antd-style';
5
5
  import { Plus } from 'lucide-react';
6
- import { ReactNode, memo } from 'react';
6
+ import { ReactNode, memo, useEffect, useRef } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  const styles = createStaticStyles(({ css, cssVar: cv }) => ({
@@ -109,15 +109,33 @@ interface ChromeTabsProps {
109
109
 
110
110
  const ChromeTabs = memo<ChromeTabsProps>(({ items, activeId, onChange, onAdd }) => {
111
111
  const { t } = useTranslation('chat');
112
+ const containerRef = useRef<HTMLDivElement>(null);
113
+
114
+ useEffect(() => {
115
+ if (!containerRef.current || !activeId) return;
116
+
117
+ const activeTab = containerRef.current.querySelector(`[data-tab-id="${activeId}"]`);
118
+ if (!activeTab) return;
119
+
120
+ const containerRect = containerRef.current.getBoundingClientRect();
121
+ const tabRect = activeTab.getBoundingClientRect();
122
+
123
+ const isVisible = tabRect.left >= containerRect.left && tabRect.right <= containerRect.right;
124
+
125
+ if (!isVisible) {
126
+ activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
127
+ }
128
+ }, [activeId]);
112
129
 
113
130
  return (
114
- <div className={styles.container}>
131
+ <div className={styles.container} ref={containerRef}>
115
132
  {items.map((item) => {
116
133
  const isActive = item.id === activeId;
117
134
 
118
135
  return (
119
136
  <div
120
137
  className={cx(styles.tab, isActive && styles.tabActive)}
138
+ data-tab-id={item.id}
121
139
  key={item.id}
122
140
  onClick={() => onChange(item.id)}
123
141
  >
@@ -5,12 +5,14 @@ import { useGroupProfileStore } from '@/store/groupProfile';
5
5
 
6
6
  /**
7
7
  * AgentTool for group profile editor
8
- * - Uses default settings (no web browsing, no filterAvailableInWeb, uses metaList)
8
+ * - showWebBrowsing: Group member profile supports web browsing toggle
9
+ * - filterAvailableInWeb: Filter out desktop-only tools in web version
10
+ * - useAllMetaList: Use allMetaList to include hidden tools
9
11
  * - Passes agentId from group profile store to display the correct member's plugins
10
12
  */
11
13
  const AgentTool = () => {
12
14
  const agentId = useGroupProfileStore((s) => s.activeTabId);
13
- return <SharedAgentTool agentId={agentId} />;
15
+ return <SharedAgentTool agentId={agentId} filterAvailableInWeb showWebBrowsing useAllMetaList />;
14
16
  };
15
17
 
16
18
  export default AgentTool;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEditor, useEditorState } from '@lobehub/editor/react';
4
4
  import { useUnmount } from 'ahooks';
5
- import { memo, useEffect, useRef } from 'react';
5
+ import { memo, useEffect } from 'react';
6
6
  import { createStoreUpdater } from 'zustand-utils';
7
7
 
8
8
  import { useRegisterFilesHotkeys, useSaveDocumentHotkey } from '@/hooks/useHotkeys';
@@ -25,34 +25,15 @@ const ProfileHydration = memo(() => {
25
25
  const [activeTabId] = useQueryState('tab', parseAsString.withDefault('group'));
26
26
  storeUpdater('activeTabId', activeTabId);
27
27
 
28
- // Bidirectional sync between URL query 'bt' and chatStore.activeTopicId
29
- const [builderTopicId, setBuilderTopicId] = useQueryState('bt');
30
- const activeTopicId = useChatStore((s) => s.activeTopicId);
28
+ // Sync URL query 'bt' chatStore.activeTopicId (one-way only)
29
+ // Store URL sync is handled directly by TopicSelector using setBuilderTopicId
30
+ const [builderTopicId] = useQueryState('bt');
31
31
 
32
- // Track if the change came from URL to prevent sync loops
33
- const isUrlChangeRef = useRef(false);
34
-
35
- // Sync URL → Store (when URL changes)
36
32
  useEffect(() => {
37
33
  const urlTopicId = builderTopicId ?? undefined;
38
- if (urlTopicId !== activeTopicId) {
39
- isUrlChangeRef.current = true;
40
- useChatStore.setState({ activeTopicId: urlTopicId });
41
- }
34
+ useChatStore.setState({ activeTopicId: urlTopicId });
42
35
  }, [builderTopicId]);
43
36
 
44
- // Sync Store → URL (when store changes, but not from URL)
45
- useEffect(() => {
46
- if (isUrlChangeRef.current) {
47
- isUrlChangeRef.current = false;
48
- return;
49
- }
50
- const urlTopicId = builderTopicId ?? undefined;
51
- if (activeTopicId !== urlTopicId) {
52
- setBuilderTopicId(activeTopicId ?? null);
53
- }
54
- }, [activeTopicId]);
55
-
56
37
  // Register hotkeys
57
38
  useRegisterFilesHotkeys();
58
39
  useSaveDocumentHotkey(flushSave);
@@ -65,7 +46,6 @@ const ProfileHydration = memo(() => {
65
46
  editorState: undefined,
66
47
  saveStateMap: {},
67
48
  });
68
- useChatStore.setState({ activeTopicId: undefined });
69
49
  });
70
50
 
71
51
  return null;
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ import { Avatar } from '@lobehub/ui';
4
+ import { memo } from 'react';
5
+
6
+ import GroupAvatar from '@/features/GroupAvatar';
7
+
8
+ export interface AgentGroupAvatarProps {
9
+ /**
10
+ * Custom avatar for the group (emoji or url)
11
+ */
12
+ avatar?: string;
13
+ /**
14
+ * Background color for custom avatar
15
+ */
16
+ backgroundColor?: string;
17
+ /**
18
+ * Member avatars to display when no custom avatar
19
+ */
20
+ memberAvatars?: { avatar?: string; background?: string }[];
21
+ /**
22
+ * Avatar size
23
+ */
24
+ size?: number;
25
+ }
26
+
27
+ const AgentGroupAvatar = memo<AgentGroupAvatarProps>(
28
+ ({ avatar, backgroundColor, memberAvatars = [], size = 28 }) => {
29
+ // If group has custom avatar, show it; otherwise show member avatars composition
30
+ if (avatar) {
31
+ return <Avatar avatar={avatar} background={backgroundColor} shape="square" size={size} />;
32
+ }
33
+
34
+ return <GroupAvatar avatars={memberAvatars} size={size} />;
35
+ },
36
+ );
37
+
38
+ export default AgentGroupAvatar;
@@ -6,9 +6,9 @@ import { type MouseEventHandler, memo, useCallback } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { MESSAGE_ACTION_BAR_PORTAL_ATTRIBUTES } from '@/const/messageActionPortal';
9
+ import AgentGroupAvatar from '@/features/AgentGroupAvatar';
9
10
  import { ChatItem } from '@/features/Conversation/ChatItem';
10
11
  import { useNewScreen } from '@/features/Conversation/Messages/components/useNewScreen';
11
- import GroupAvatar from '@/features/GroupAvatar';
12
12
  import { useAgentGroupStore } from '@/store/agentGroup';
13
13
  import { agentGroupSelectors } from '@/store/agentGroup/selectors';
14
14
 
@@ -90,7 +90,13 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLat
90
90
  </>
91
91
  }
92
92
  avatar={{ ...avatar, title: groupMeta.title }}
93
- customAvatarRender={() => <GroupAvatar avatars={memberAvatars} />}
93
+ customAvatarRender={() => (
94
+ <AgentGroupAvatar
95
+ avatar={groupMeta.avatar}
96
+ backgroundColor={groupMeta.backgroundColor}
97
+ memberAvatars={memberAvatars}
98
+ />
99
+ )}
94
100
  newScreen={newScreen}
95
101
  onMouseEnter={onMouseEnter}
96
102
  placement={'left'}
@@ -30,12 +30,11 @@ export const useMarkdown = (id: string): Partial<MarkdownProps> => {
30
30
  () =>
31
31
  ({
32
32
  components: Object.fromEntries(
33
- // @ts-expect-error
34
33
  markdownElements.map((element) => {
35
34
  const Component = element.Component;
36
35
  return [element.tag, (props: any) => <Component {...props} id={id} />];
37
36
  }),
38
- ),
37
+ ) as any,
39
38
  customRender: (dom: ReactNode, { text }: { text: string }) => {
40
39
  if (text.length > 30_000) return <ContentPreview content={text} id={id} />;
41
40
  return dom;