@lobehub/lobehub 2.0.0-next.284 → 2.0.0-next.286

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 (38) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/setting.json +1 -0
  4. package/locales/en-US/subscription.json +2 -0
  5. package/locales/zh-CN/setting.json +1 -0
  6. package/locales/zh-CN/subscription.json +2 -0
  7. package/package.json +1 -1
  8. package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +79 -2
  9. package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +66 -5
  10. package/packages/builtin-tool-agent-builder/src/client/Render/InstallPlugin.tsx +12 -4
  11. package/packages/builtin-tool-agent-builder/src/manifest.ts +3 -2
  12. package/packages/builtin-tool-agent-builder/src/systemRole.ts +8 -8
  13. package/packages/builtin-tool-agent-builder/src/types.ts +7 -3
  14. package/packages/business/const/src/index.ts +0 -3
  15. package/packages/context-engine/src/providers/AgentBuilderContextInjector.ts +20 -4
  16. package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -2
  17. package/packages/model-bank/src/aiModels/lobehub.ts +20 -1
  18. package/packages/model-runtime/src/providers/fal/index.test.ts +176 -1
  19. package/packages/model-runtime/src/providers/fal/index.ts +3 -1
  20. package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/AddButton.tsx +2 -1
  21. package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +5 -3
  22. package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/MessageFromUrl.tsx +57 -9
  23. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishButton.tsx +5 -8
  24. package/src/app/[variants]/(main)/agent/profile/features/Header/index.tsx +0 -2
  25. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +2 -0
  26. package/src/app/[variants]/(main)/community/(detail)/provider/features/Details/Nav.tsx +27 -18
  27. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +5 -3
  28. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/MessageFromUrl.tsx +24 -10
  29. package/src/app/[variants]/onboarding/_layout/style.ts +10 -20
  30. package/src/features/ProfileEditor/AgentTool.tsx +68 -12
  31. package/src/features/ProfileEditor/PluginTag.tsx +56 -3
  32. package/src/locales/default/setting.ts +1 -0
  33. package/src/locales/default/subscription.ts +2 -0
  34. package/src/services/chat/index.ts +24 -1
  35. package/src/services/chat/mecha/contextEngineering.ts +28 -2
  36. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +11 -1
  37. package/src/store/user/slices/settings/action.test.ts +25 -0
  38. package/src/store/user/slices/settings/action.ts +11 -0
@@ -479,7 +479,7 @@ describe('LobeFalAI', () => {
479
479
  });
480
480
  });
481
481
 
482
- describe('Seedream v4 special endpoints', () => {
482
+ describe('Seedream and Hunyuan special endpoints', () => {
483
483
  it('should use text-to-image endpoint when no imageUrls provided', async () => {
484
484
  // Arrange
485
485
  const mockImageResponse = {
@@ -698,6 +698,181 @@ describe('LobeFalAI', () => {
698
698
  },
699
699
  });
700
700
  });
701
+
702
+ it('should use text-to-image endpoint for seedream v4.5 when no imageUrls provided', async () => {
703
+ // Arrange
704
+ const mockImageResponse = {
705
+ requestId: 'test-request-id',
706
+ data: {
707
+ images: [
708
+ {
709
+ url: 'https://example.com/generated.jpg',
710
+ width: 2048,
711
+ height: 2048,
712
+ },
713
+ ],
714
+ },
715
+ };
716
+ mockFal.subscribe.mockResolvedValue(mockImageResponse as any);
717
+
718
+ const payload: CreateImagePayload = {
719
+ model: 'bytedance/seedream/v4.5',
720
+ params: {
721
+ prompt: 'A beautiful landscape',
722
+ width: 2048,
723
+ height: 2048,
724
+ },
725
+ };
726
+
727
+ // Act
728
+ await instance.createImage(payload);
729
+
730
+ // Assert
731
+ expect(mockFal.subscribe).toHaveBeenCalledWith(
732
+ 'fal-ai/bytedance/seedream/v4.5/text-to-image',
733
+ {
734
+ input: {
735
+ enable_safety_checker: false,
736
+ num_images: 1,
737
+ prompt: 'A beautiful landscape',
738
+ image_size: {
739
+ width: 2048,
740
+ height: 2048,
741
+ },
742
+ },
743
+ },
744
+ );
745
+ });
746
+
747
+ it('should use edit endpoint for seedream v4.5 when imageUrls is provided', async () => {
748
+ // Arrange
749
+ const mockImageResponse = {
750
+ requestId: 'test-request-id',
751
+ data: {
752
+ images: [
753
+ {
754
+ url: 'https://example.com/edited.jpg',
755
+ width: 2048,
756
+ height: 2048,
757
+ },
758
+ ],
759
+ },
760
+ };
761
+ mockFal.subscribe.mockResolvedValue(mockImageResponse as any);
762
+
763
+ const payload: CreateImagePayload = {
764
+ model: 'bytedance/seedream/v4.5',
765
+ params: {
766
+ prompt: 'Edit this image',
767
+ imageUrls: ['https://example.com/input.jpg'],
768
+ width: 2048,
769
+ height: 2048,
770
+ },
771
+ };
772
+
773
+ // Act
774
+ await instance.createImage(payload);
775
+
776
+ // Assert
777
+ expect(mockFal.subscribe).toHaveBeenCalledWith('fal-ai/bytedance/seedream/v4.5/edit', {
778
+ input: {
779
+ enable_safety_checker: false,
780
+ num_images: 1,
781
+ prompt: 'Edit this image',
782
+ image_urls: ['https://example.com/input.jpg'],
783
+ image_size: {
784
+ width: 2048,
785
+ height: 2048,
786
+ },
787
+ },
788
+ });
789
+ });
790
+
791
+ it('should use text-to-image endpoint for hunyuan-image v3 when no imageUrls provided', async () => {
792
+ // Arrange
793
+ const mockImageResponse = {
794
+ requestId: 'test-request-id',
795
+ data: {
796
+ images: [
797
+ {
798
+ url: 'https://example.com/generated.jpg',
799
+ width: 1024,
800
+ height: 1024,
801
+ },
802
+ ],
803
+ },
804
+ };
805
+ mockFal.subscribe.mockResolvedValue(mockImageResponse as any);
806
+
807
+ const payload: CreateImagePayload = {
808
+ model: 'hunyuan-image/v3',
809
+ params: {
810
+ prompt: 'A scenic mountain view',
811
+ width: 1024,
812
+ height: 1024,
813
+ },
814
+ };
815
+
816
+ // Act
817
+ await instance.createImage(payload);
818
+
819
+ // Assert
820
+ expect(mockFal.subscribe).toHaveBeenCalledWith('fal-ai/hunyuan-image/v3/text-to-image', {
821
+ input: {
822
+ enable_safety_checker: false,
823
+ num_images: 1,
824
+ prompt: 'A scenic mountain view',
825
+ image_size: {
826
+ width: 1024,
827
+ height: 1024,
828
+ },
829
+ },
830
+ });
831
+ });
832
+
833
+ it('should use edit endpoint for hunyuan-image v3 when imageUrls is provided', async () => {
834
+ // Arrange
835
+ const mockImageResponse = {
836
+ requestId: 'test-request-id',
837
+ data: {
838
+ images: [
839
+ {
840
+ url: 'https://example.com/edited.jpg',
841
+ width: 1024,
842
+ height: 1024,
843
+ },
844
+ ],
845
+ },
846
+ };
847
+ mockFal.subscribe.mockResolvedValue(mockImageResponse as any);
848
+
849
+ const payload: CreateImagePayload = {
850
+ model: 'hunyuan-image/v3',
851
+ params: {
852
+ prompt: 'Edit this image',
853
+ imageUrls: ['https://example.com/input.jpg'],
854
+ width: 1024,
855
+ height: 1024,
856
+ },
857
+ };
858
+
859
+ // Act
860
+ await instance.createImage(payload);
861
+
862
+ // Assert
863
+ expect(mockFal.subscribe).toHaveBeenCalledWith('fal-ai/hunyuan-image/v3/edit', {
864
+ input: {
865
+ enable_safety_checker: false,
866
+ num_images: 1,
867
+ prompt: 'Edit this image',
868
+ image_urls: ['https://example.com/input.jpg'],
869
+ image_size: {
870
+ width: 1024,
871
+ height: 1024,
872
+ },
873
+ },
874
+ });
875
+ });
701
876
  });
702
877
 
703
878
  describe('Edge cases', () => {
@@ -71,7 +71,9 @@ export class LobeFalAI implements LobeRuntimeAI {
71
71
  // Ensure model has fal-ai/ prefix
72
72
  let endpoint = model.startsWith('fal-ai/') ? model : `fal-ai/${model}`;
73
73
  const hasImageUrls = (params.imageUrls?.length ?? 0) > 0;
74
- if (['fal-ai/bytedance/seedream/v4', 'fal-ai/hunyuan-image/v3'].includes(endpoint)) {
74
+ if (
75
+ ['fal-ai/bytedance/seedream/v', 'fal-ai/hunyuan-image/v'].some((m) => endpoint.startsWith(m))
76
+ ) {
75
77
  endpoint += hasImageUrls ? '/edit' : '/text-to-image';
76
78
  } else if (endpoint === 'fal-ai/nano-banana' && hasImageUrls) {
77
79
  endpoint += '/edit';
@@ -9,7 +9,8 @@ import { useAgentStore } from '@/store/agent';
9
9
  const AddButton = memo(() => {
10
10
  const navigate = useNavigate();
11
11
  const createAgent = useAgentStore((s) => s.createAgent);
12
- const { mutate, isValidating } = useActionSWR('agent.createAgent', async () => {
12
+ // Use a unique SWR key to avoid conflicts with useCreateMenuItems which uses 'agent.createAgent'
13
+ const { mutate, isValidating } = useActionSWR('agent.createAgentFromWelcome', async () => {
13
14
  const result = await createAgent({});
14
15
  navigate(`/agent/${result.agentId}/profile`);
15
16
  return result;
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const';
4
3
  import { ActionIcon } from '@lobehub/ui';
5
4
  import { Share2 } from 'lucide-react';
6
5
  import dynamic from 'next/dynamic';
@@ -10,6 +9,8 @@ import { useTranslation } from 'react-i18next';
10
9
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
11
10
  import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
12
11
  import { useChatStore } from '@/store/chat';
12
+ import { useServerConfigStore } from '@/store/serverConfig';
13
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
13
14
 
14
15
  const ShareModal = dynamic(() => import('@/features/ShareModal'));
15
16
  const SharePopover = dynamic(() => import('@/features/SharePopover'));
@@ -24,6 +25,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
24
25
  const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen);
25
26
  const { t } = useTranslation('common');
26
27
  const activeTopicId = useChatStore((s) => s.activeTopicId);
28
+ const enableTopicLinkShare = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
27
29
 
28
30
  // Hide share button when no topic exists (no messages sent yet)
29
31
  if (!activeTopicId) return null;
@@ -31,7 +33,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
31
33
  const iconButton = (
32
34
  <ActionIcon
33
35
  icon={Share2}
34
- onClick={ENABLE_TOPIC_LINK_SHARE ? undefined : () => setIsModalOpen(true)}
36
+ onClick={enableTopicLinkShare ? undefined : () => setIsModalOpen(true)}
35
37
  size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
36
38
  title={t('share')}
37
39
  tooltipProps={{
@@ -42,7 +44,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
42
44
 
43
45
  return (
44
46
  <>
45
- {ENABLE_TOPIC_LINK_SHARE ? (
47
+ {enableTopicLinkShare ? (
46
48
  <SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
47
49
  ) : (
48
50
  iconButton
@@ -1,9 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { useEffect } from 'react';
4
- import { useSearchParams } from 'react-router-dom';
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import { useLocation, useSearchParams } from 'react-router-dom';
5
5
 
6
6
  import { useConversationStore } from '@/features/Conversation';
7
+ import { useAgentStore } from '@/store/agent';
8
+ import { agentSelectors } from '@/store/agent/selectors';
7
9
 
8
10
  /**
9
11
  * MessageFromUrl
@@ -12,23 +14,69 @@ import { useConversationStore } from '@/features/Conversation';
12
14
  * Uses ConversationStore for input and send operations.
13
15
  */
14
16
  const MessageFromUrl = () => {
15
- const [updateInputMessage, sendMessage] = useConversationStore((s) => [
16
- s.updateInputMessage,
17
+ const [sendMessage, context, messagesInit] = useConversationStore((s) => [
17
18
  s.sendMessage,
19
+ s.context,
20
+ s.messagesInit,
18
21
  ]);
22
+ const agentId = context.agentId;
19
23
  const [searchParams, setSearchParams] = useSearchParams();
24
+ const location = useLocation();
25
+ const isAgentConfigLoading = useAgentStore(agentSelectors.isAgentConfigLoading);
26
+
27
+ const routeAgentId = useMemo(() => {
28
+ const match = location.pathname?.match(/^\/agent\/([^#/?]+)/);
29
+ return match?.[1];
30
+ }, [location.pathname]);
31
+
32
+ // Track last processed (agentId, message) to prevent duplicate sends on re-render,
33
+ // while still allowing sending when navigating to a different agent (or message).
34
+ const lastProcessedSignatureRef = useRef<string | null>(null);
20
35
 
21
36
  useEffect(() => {
22
37
  const message = searchParams.get('message');
23
38
  if (!message) return;
24
39
 
25
- const params = new URLSearchParams(searchParams.toString());
26
- params.delete('message');
27
- setSearchParams(params, { replace: true });
40
+ // Wait for agentId to be available before sending
41
+ if (!agentId) return;
42
+
43
+ // During agent switching, URL/searchParams may update before ConversationStore context updates.
44
+ // Only consume the param when the route agentId matches the ConversationStore agentId.
45
+ if (routeAgentId && routeAgentId !== agentId) return;
46
+
47
+ // Ensure required agent info is loaded before consuming the param.
48
+ // For existing conversations (topicId exists), also wait until messages are initialized
49
+ // to avoid sending during skeleton fetch states.
50
+ const isNewConversation = !context.topicId;
51
+ const isReady = !isAgentConfigLoading && (isNewConversation || messagesInit);
52
+ if (!isReady) return;
28
53
 
29
- updateInputMessage(message);
54
+ const signature = `${agentId}::${message}`;
55
+ if (lastProcessedSignatureRef.current === signature) return;
56
+ lastProcessedSignatureRef.current = signature;
57
+
58
+ // Use functional update to safely remove message param without affecting other params
59
+ setSearchParams(
60
+ (prev) => {
61
+ const newParams = new URLSearchParams(prev);
62
+ newParams.delete('message');
63
+ return newParams;
64
+ },
65
+ { replace: true },
66
+ );
67
+
68
+ // Send the message
30
69
  sendMessage({ message });
31
- }, [searchParams, setSearchParams, updateInputMessage, sendMessage]);
70
+ }, [
71
+ searchParams,
72
+ setSearchParams,
73
+ sendMessage,
74
+ agentId,
75
+ context.topicId,
76
+ isAgentConfigLoading,
77
+ messagesInit,
78
+ routeAgentId,
79
+ ]);
32
80
 
33
81
  return null;
34
82
  };
@@ -1,13 +1,11 @@
1
- import { ActionIcon } from '@lobehub/ui';
1
+ import { Button } from '@lobehub/ui';
2
2
  import { ShapesUploadIcon } from '@lobehub/ui/icons';
3
3
  import { memo, useCallback, useMemo, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { message } from '@/components/AntdStaticMethods';
7
- import { HEADER_ICON_SIZE } from '@/const/layoutTokens';
8
7
  import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
9
8
  import { resolveMarketAuthError } from '@/layout/AuthProvider/MarketAuth/errors';
10
- import { useServerConfigStore } from '@/store/serverConfig';
11
9
 
12
10
  import ForkConfirmModal from './ForkConfirmModal';
13
11
  import type { MarketPublishAction } from './types';
@@ -21,8 +19,6 @@ interface MarketPublishButtonProps {
21
19
  const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess }) => {
22
20
  const { t } = useTranslation(['setting', 'marketAuth']);
23
21
 
24
- const mobile = useServerConfigStore((s) => s.isMobile);
25
-
26
22
  const { isAuthenticated, isLoading, signIn } = useMarketAuth();
27
23
  const { checkOwnership, isCheckingOwnership, isPublishing, publish } = useMarketPublish({
28
24
  action,
@@ -102,13 +98,14 @@ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess
102
98
 
103
99
  return (
104
100
  <>
105
- <ActionIcon
101
+ <Button
106
102
  icon={ShapesUploadIcon}
107
103
  loading={loading}
108
104
  onClick={handleButtonClick}
109
- size={HEADER_ICON_SIZE(mobile)}
110
105
  title={buttonTitle}
111
- />
106
+ >
107
+ {t('publishToCommunity')}
108
+ </Button>
112
109
  <ForkConfirmModal
113
110
  loading={isPublishing}
114
111
  onCancel={handleForkCancel}
@@ -5,7 +5,6 @@ import NavHeader from '@/features/NavHeader';
5
5
  import ToggleRightPanelButton from '@/features/RightPanel/ToggleRightPanelButton';
6
6
  import WideScreenButton from '@/features/WideScreenContainer/WideScreenButton';
7
7
 
8
- import AgentPublishButton from './AgentPublishButton';
9
8
  import AutoSaveHint from './AutoSaveHint';
10
9
 
11
10
  const Header = memo(() => {
@@ -16,7 +15,6 @@ const Header = memo(() => {
16
15
  <>
17
16
  <WideScreenButton />
18
17
  <ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} />
19
- <AgentPublishButton />
20
18
  </>
21
19
  }
22
20
  />
@@ -17,6 +17,7 @@ import { useChatStore } from '@/store/chat';
17
17
 
18
18
  import AgentCronJobs from '../AgentCronJobs';
19
19
  import EditorCanvas from '../EditorCanvas';
20
+ import AgentPublishButton from '../Header/AgentPublishButton';
20
21
  import AgentHeader from './AgentHeader';
21
22
  import AgentTool from './AgentTool';
22
23
 
@@ -79,6 +80,7 @@ const ProfileEditor = memo(() => {
79
80
  >
80
81
  {t('startConversation')}
81
82
  </Button>
83
+ <AgentPublishButton />
82
84
  {ENABLE_BUSINESS_FEATURES && (
83
85
  <Button icon={Clock} onClick={handleCreateCronJob}>
84
86
  {t('agentCronJobs.addJob')}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { SOCIAL_URL } from '@lobechat/business-const';
3
+ import { BRANDING_PROVIDER, SOCIAL_URL } from '@lobechat/business-const';
4
4
  import { Flexbox, Icon, Tabs } from '@lobehub/ui';
5
5
  import { createStaticStyles } from 'antd-style';
6
6
  import { BookOpenIcon, BrainCircuitIcon, ListIcon } from 'lucide-react';
@@ -38,27 +38,36 @@ const Nav = memo<NavProps>(({ mobile, setActiveTab, activeTab = ProviderNavKey.O
38
38
  const { t } = useTranslation('discover');
39
39
  const { identifier } = useDetailContext();
40
40
 
41
+ // Hide Guide tab for branding provider as it doesn't have integration docs
42
+ const showGuideTab = identifier !== BRANDING_PROVIDER;
43
+
44
+ const items = [
45
+ {
46
+ icon: <Icon icon={BookOpenIcon} size={16} />,
47
+ key: ProviderNavKey.Overview,
48
+ label: t('providers.details.overview.title'),
49
+ },
50
+ ...(showGuideTab
51
+ ? [
52
+ {
53
+ icon: <Icon icon={BrainCircuitIcon} size={16} />,
54
+ key: ProviderNavKey.Guide,
55
+ label: t('providers.details.guide.title'),
56
+ },
57
+ ]
58
+ : []),
59
+ {
60
+ icon: <Icon icon={ListIcon} size={16} />,
61
+ key: ProviderNavKey.Related,
62
+ label: t('providers.details.related.title'),
63
+ },
64
+ ];
65
+
41
66
  const nav = (
42
67
  <Tabs
43
68
  activeKey={activeTab}
44
69
  compact={mobile}
45
- items={[
46
- {
47
- icon: <Icon icon={BookOpenIcon} size={16} />,
48
- key: ProviderNavKey.Overview,
49
- label: t('providers.details.overview.title'),
50
- },
51
- {
52
- icon: <Icon icon={BrainCircuitIcon} size={16} />,
53
- key: ProviderNavKey.Guide,
54
- label: t('providers.details.guide.title'),
55
- },
56
- {
57
- icon: <Icon icon={ListIcon} size={16} />,
58
- key: ProviderNavKey.Related,
59
- label: t('providers.details.related.title'),
60
- },
61
- ]}
70
+ items={items}
62
71
  onChange={(key) => setActiveTab?.(key as ProviderNavKey)}
63
72
  />
64
73
  );
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const';
4
3
  import { ActionIcon } from '@lobehub/ui';
5
4
  import { Share2 } from 'lucide-react';
6
5
  import dynamic from 'next/dynamic';
@@ -10,6 +9,8 @@ import { useTranslation } from 'react-i18next';
10
9
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
11
10
  import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
12
11
  import { useChatStore } from '@/store/chat';
12
+ import { useServerConfigStore } from '@/store/serverConfig';
13
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
13
14
 
14
15
  const ShareModal = dynamic(() => import('@/features/ShareModal'));
15
16
  const SharePopover = dynamic(() => import('@/features/SharePopover'));
@@ -24,6 +25,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
24
25
  const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen);
25
26
  const { t } = useTranslation('common');
26
27
  const activeTopicId = useChatStore((s) => s.activeTopicId);
28
+ const enableTopicLinkShare = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
27
29
 
28
30
  // Hide share button when no topic exists (no messages sent yet)
29
31
  if (!activeTopicId) return null;
@@ -31,7 +33,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
31
33
  const iconButton = (
32
34
  <ActionIcon
33
35
  icon={Share2}
34
- onClick={ENABLE_TOPIC_LINK_SHARE ? undefined : () => setIsModalOpen(true)}
36
+ onClick={enableTopicLinkShare ? undefined : () => setIsModalOpen(true)}
35
37
  size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
36
38
  title={t('share')}
37
39
  tooltipProps={{
@@ -42,7 +44,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
42
44
 
43
45
  return (
44
46
  <>
45
- {ENABLE_TOPIC_LINK_SHARE ? (
47
+ {enableTopicLinkShare ? (
46
48
  <SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
47
49
  ) : (
48
50
  iconButton
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect } from 'react';
3
+ import { useEffect, useRef } from 'react';
4
4
  import { useSearchParams } from 'react-router-dom';
5
5
 
6
6
  import { useConversationStore } from '@/features/Conversation';
@@ -12,23 +12,37 @@ import { useConversationStore } from '@/features/Conversation';
12
12
  * Uses ConversationStore for input and send operations.
13
13
  */
14
14
  const MessageFromUrl = () => {
15
- const [updateInputMessage, sendMessage] = useConversationStore((s) => [
16
- s.updateInputMessage,
17
- s.sendMessage,
18
- ]);
15
+ const [sendMessage, agentId] = useConversationStore((s) => [s.sendMessage, s.context.agentId]);
19
16
  const [searchParams, setSearchParams] = useSearchParams();
20
17
 
18
+ // Track if we've processed the initial message to prevent duplicate sends
19
+ const hasProcessedRef = useRef(false);
20
+
21
21
  useEffect(() => {
22
+ // Only process once
23
+ if (hasProcessedRef.current) return;
24
+
22
25
  const message = searchParams.get('message');
23
26
  if (!message) return;
24
27
 
25
- const params = new URLSearchParams(searchParams.toString());
26
- params.delete('message');
27
- setSearchParams(params, { replace: true });
28
+ // Wait for agentId to be available before sending
29
+ if (!agentId) return;
30
+
31
+ hasProcessedRef.current = true;
32
+
33
+ // Use functional update to safely remove message param without affecting other params
34
+ setSearchParams(
35
+ (prev) => {
36
+ const newParams = new URLSearchParams(prev);
37
+ newParams.delete('message');
38
+ return newParams;
39
+ },
40
+ { replace: true },
41
+ );
28
42
 
29
- updateInputMessage(message);
43
+ // Send the message
30
44
  sendMessage({ message });
31
- }, [searchParams, setSearchParams, updateInputMessage, sendMessage]);
45
+ }, [searchParams, setSearchParams, sendMessage, agentId]);
32
46
 
33
47
  return null;
34
48
  };
@@ -1,21 +1,16 @@
1
1
  import { createStaticStyles } from 'antd-style';
2
2
 
3
3
  export const styles = createStaticStyles(({ css, cssVar }) => ({
4
-
5
-
6
- // Divider 样式
7
- divider: css`
4
+ // Divider 样式
5
+ divider: css`
8
6
  height: 24px;
9
7
  `,
10
8
 
11
-
12
-
13
-
14
- // 内层容器 - 深色模式
15
- innerContainerDark: css`
9
+ // 内层容器 - 深色模式
10
+ innerContainerDark: css`
16
11
  position: relative;
17
12
 
18
- overflow: hidden;
13
+ overflow: hidden auto;
19
14
 
20
15
  border: 1px solid ${cssVar.colorBorderSecondary};
21
16
  border-radius: ${cssVar.borderRadius};
@@ -23,14 +18,11 @@ innerContainerDark: css`
23
18
  background: ${cssVar.colorBgContainer};
24
19
  `,
25
20
 
26
-
27
-
28
-
29
- // 内层容器 - 浅色模式
30
- innerContainerLight: css`
21
+ // 内层容器 - 浅色模式
22
+ innerContainerLight: css`
31
23
  position: relative;
32
24
 
33
- overflow: hidden;
25
+ overflow: hidden auto;
34
26
 
35
27
  border: 1px solid ${cssVar.colorBorder};
36
28
  border-radius: ${cssVar.borderRadius};
@@ -38,10 +30,8 @@ innerContainerLight: css`
38
30
  background: ${cssVar.colorBgContainer};
39
31
  `,
40
32
 
41
-
42
-
43
- // 外层容器
44
- outerContainer: css`
33
+ // 外层容器
34
+ outerContainer: css`
45
35
  position: relative;
46
36
  `,
47
37
  }));