🐛 fix: prevent auto navigation to profile when clicking topic (#11500)

This commit is contained in:
YuTengjing
2026-01-14 21:14:11 +08:00
committed by GitHub
parent ba58756cd0
commit 1e03005e0e
8 changed files with 85 additions and 48 deletions

View File

@@ -4,6 +4,3 @@ export * from './llm';
export * from './url';
export const ENABLE_BUSINESS_FEATURES = false;
export const ENABLE_TOPIC_LINK_SHARE =
ENABLE_BUSINESS_FEATURES ||
(process.env.NODE_ENV === 'development' && !!process.env.NEXT_PUBLIC_ENABLE_TOPIC_LINK_SHARE);

View File

@@ -9,7 +9,8 @@ import { useAgentStore } from '@/store/agent';
const AddButton = memo(() => {
const navigate = useNavigate();
const createAgent = useAgentStore((s) => s.createAgent);
const { mutate, isValidating } = useActionSWR('agent.createAgent', async () => {
// Use a unique SWR key to avoid conflicts with useCreateMenuItems which uses 'agent.createAgent'
const { mutate, isValidating } = useActionSWR('agent.createAgentFromWelcome', async () => {
const result = await createAgent({});
navigate(`/agent/${result.agentId}/profile`);
return result;

View File

@@ -1,6 +1,5 @@
'use client';
import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const';
import { ActionIcon } from '@lobehub/ui';
import { Share2 } from 'lucide-react';
import dynamic from 'next/dynamic';
@@ -10,6 +9,8 @@ import { useTranslation } from 'react-i18next';
import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
import { useChatStore } from '@/store/chat';
import { useServerConfigStore } from '@/store/serverConfig';
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
const ShareModal = dynamic(() => import('@/features/ShareModal'));
const SharePopover = dynamic(() => import('@/features/SharePopover'));
@@ -24,6 +25,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen);
const { t } = useTranslation('common');
const activeTopicId = useChatStore((s) => s.activeTopicId);
const enableTopicLinkShare = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
// Hide share button when no topic exists (no messages sent yet)
if (!activeTopicId) return null;
@@ -31,7 +33,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
const iconButton = (
<ActionIcon
icon={Share2}
onClick={ENABLE_TOPIC_LINK_SHARE ? undefined : () => setIsModalOpen(true)}
onClick={enableTopicLinkShare ? undefined : () => setIsModalOpen(true)}
size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
title={t('share')}
tooltipProps={{
@@ -42,7 +44,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
return (
<>
{ENABLE_TOPIC_LINK_SHARE ? (
{enableTopicLinkShare ? (
<SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
) : (
iconButton

View File

@@ -1,6 +1,6 @@
'use client';
import { SOCIAL_URL } from '@lobechat/business-const';
import { BRANDING_PROVIDER, SOCIAL_URL } from '@lobechat/business-const';
import { Flexbox, Icon, Tabs } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { BookOpenIcon, BrainCircuitIcon, ListIcon } from 'lucide-react';
@@ -38,27 +38,36 @@ const Nav = memo<NavProps>(({ mobile, setActiveTab, activeTab = ProviderNavKey.O
const { t } = useTranslation('discover');
const { identifier } = useDetailContext();
// Hide Guide tab for branding provider as it doesn't have integration docs
const showGuideTab = identifier !== BRANDING_PROVIDER;
const items = [
{
icon: <Icon icon={BookOpenIcon} size={16} />,
key: ProviderNavKey.Overview,
label: t('providers.details.overview.title'),
},
...(showGuideTab
? [
{
icon: <Icon icon={BrainCircuitIcon} size={16} />,
key: ProviderNavKey.Guide,
label: t('providers.details.guide.title'),
},
]
: []),
{
icon: <Icon icon={ListIcon} size={16} />,
key: ProviderNavKey.Related,
label: t('providers.details.related.title'),
},
];
const nav = (
<Tabs
activeKey={activeTab}
compact={mobile}
items={[
{
icon: <Icon icon={BookOpenIcon} size={16} />,
key: ProviderNavKey.Overview,
label: t('providers.details.overview.title'),
},
{
icon: <Icon icon={BrainCircuitIcon} size={16} />,
key: ProviderNavKey.Guide,
label: t('providers.details.guide.title'),
},
{
icon: <Icon icon={ListIcon} size={16} />,
key: ProviderNavKey.Related,
label: t('providers.details.related.title'),
},
]}
items={items}
onChange={(key) => setActiveTab?.(key as ProviderNavKey)}
/>
);

View File

@@ -1,6 +1,5 @@
'use client';
import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const';
import { ActionIcon } from '@lobehub/ui';
import { Share2 } from 'lucide-react';
import dynamic from 'next/dynamic';
@@ -10,6 +9,8 @@ import { useTranslation } from 'react-i18next';
import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
import { useChatStore } from '@/store/chat';
import { useServerConfigStore } from '@/store/serverConfig';
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
const ShareModal = dynamic(() => import('@/features/ShareModal'));
const SharePopover = dynamic(() => import('@/features/SharePopover'));
@@ -24,6 +25,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen);
const { t } = useTranslation('common');
const activeTopicId = useChatStore((s) => s.activeTopicId);
const enableTopicLinkShare = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
// Hide share button when no topic exists (no messages sent yet)
if (!activeTopicId) return null;
@@ -31,7 +33,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
const iconButton = (
<ActionIcon
icon={Share2}
onClick={ENABLE_TOPIC_LINK_SHARE ? undefined : () => setIsModalOpen(true)}
onClick={enableTopicLinkShare ? undefined : () => setIsModalOpen(true)}
size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
title={t('share')}
tooltipProps={{
@@ -42,7 +44,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
return (
<>
{ENABLE_TOPIC_LINK_SHARE ? (
{enableTopicLinkShare ? (
<SharePopover onOpenModal={() => setIsModalOpen(true)}>{iconButton}</SharePopover>
) : (
iconButton

View File

@@ -1,21 +1,16 @@
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
// Divider 样式
divider: css`
// Divider 样式
divider: css`
height: 24px;
`,
// 内层容器 - 深色模式
innerContainerDark: css`
// 内层容器 - 深色模式
innerContainerDark: css`
position: relative;
overflow: hidden;
overflow: hidden auto;
border: 1px solid ${cssVar.colorBorderSecondary};
border-radius: ${cssVar.borderRadius};
@@ -23,14 +18,11 @@ innerContainerDark: css`
background: ${cssVar.colorBgContainer};
`,
// 内层容器 - 浅色模式
innerContainerLight: css`
// 内层容器 - 浅色模式
innerContainerLight: css`
position: relative;
overflow: hidden;
overflow: hidden auto;
border: 1px solid ${cssVar.colorBorder};
border-radius: ${cssVar.borderRadius};
@@ -38,10 +30,8 @@ innerContainerLight: css`
background: ${cssVar.colorBgContainer};
`,
// 外层容器
outerContainer: css`
// 外层容器
outerContainer: css`
position: relative;
`,
}));

View File

@@ -84,6 +84,31 @@ describe('SettingsAction', () => {
expect.any(AbortSignal),
);
});
it('should include field in diffs when user resets it to default value', async () => {
const { result } = renderHook(() => useUserStore());
// First, set memory.enabled to false (non-default value)
await act(async () => {
await result.current.setSettings({ memory: { enabled: false } });
});
expect(userService.updateUserSettings).toHaveBeenLastCalledWith(
expect.objectContaining({ memory: { enabled: false } }),
expect.any(AbortSignal),
);
// Then, reset memory.enabled back to true (default value)
// This should still include memory in the diffs to override the previously saved value
await act(async () => {
await result.current.setSettings({ memory: { enabled: true } });
});
expect(userService.updateUserSettings).toHaveBeenLastCalledWith(
expect.objectContaining({ memory: { enabled: true } }),
expect.any(AbortSignal),
);
});
});
describe('updateDefaultAgent', () => {

View File

@@ -103,6 +103,17 @@ export const createSettingsSlice: StateCreator<
if (isEqual(prevSetting, nextSettings)) return;
const diffs = difference(nextSettings, defaultSettings);
// When user resets a field to default value, we need to explicitly include it in diffs
// to override the previously saved non-default value in the backend
const changedFields = difference(nextSettings, prevSetting);
for (const key of Object.keys(changedFields)) {
// Only handle fields that were previously set by user (exist in prevSetting)
if (key in prevSetting && !(key in diffs)) {
(diffs as any)[key] = (nextSettings as any)[key];
}
}
set({ settings: diffs }, false, 'optimistic_updateSettings');
const abortController = get().internal_createSignal();