@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/setting.json +1 -0
- package/locales/en-US/subscription.json +2 -0
- package/locales/zh-CN/setting.json +1 -0
- package/locales/zh-CN/subscription.json +2 -0
- package/package.json +1 -1
- package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +79 -2
- package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +66 -5
- package/packages/builtin-tool-agent-builder/src/client/Render/InstallPlugin.tsx +12 -4
- package/packages/builtin-tool-agent-builder/src/manifest.ts +3 -2
- package/packages/builtin-tool-agent-builder/src/systemRole.ts +8 -8
- package/packages/builtin-tool-agent-builder/src/types.ts +7 -3
- package/packages/business/const/src/index.ts +0 -3
- package/packages/context-engine/src/providers/AgentBuilderContextInjector.ts +20 -4
- package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -2
- package/packages/model-bank/src/aiModels/lobehub.ts +20 -1
- package/packages/model-runtime/src/providers/fal/index.test.ts +176 -1
- package/packages/model-runtime/src/providers/fal/index.ts +3 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/AddButton.tsx +2 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +5 -3
- package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/MessageFromUrl.tsx +57 -9
- package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishButton.tsx +5 -8
- package/src/app/[variants]/(main)/agent/profile/features/Header/index.tsx +0 -2
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +2 -0
- package/src/app/[variants]/(main)/community/(detail)/provider/features/Details/Nav.tsx +27 -18
- package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +5 -3
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/MessageFromUrl.tsx +24 -10
- package/src/app/[variants]/onboarding/_layout/style.ts +10 -20
- package/src/features/ProfileEditor/AgentTool.tsx +68 -12
- package/src/features/ProfileEditor/PluginTag.tsx +56 -3
- package/src/locales/default/setting.ts +1 -0
- package/src/locales/default/subscription.ts +2 -0
- package/src/services/chat/index.ts +24 -1
- package/src/services/chat/mecha/contextEngineering.ts +28 -2
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +11 -1
- package/src/store/user/slices/settings/action.test.ts +25 -0
- 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
|
|
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 (
|
|
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
|
-
|
|
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={
|
|
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
|
-
{
|
|
47
|
+
{enableTopicLinkShare ? (
|
|
46
48
|
<SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
|
|
47
49
|
) : (
|
|
48
50
|
iconButton
|
package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/MessageFromUrl.tsx
CHANGED
|
@@ -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 [
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
};
|
package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishButton.tsx
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
-
<
|
|
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={
|
|
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
|
-
{
|
|
47
|
+
{enableTopicLinkShare ? (
|
|
46
48
|
<SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
|
|
47
49
|
) : (
|
|
48
50
|
iconButton
|
package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/MessageFromUrl.tsx
CHANGED
|
@@ -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 [
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
43
|
+
// Send the message
|
|
30
44
|
sendMessage({ message });
|
|
31
|
-
}, [searchParams, setSearchParams,
|
|
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
|
}));
|