mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix: fixed the pinned session not work (#10323)
* fix: fixed the pinned session not work * feat: add urlHydration store to slove the url sync problem
This commit is contained in:
@@ -2,11 +2,12 @@ import { ActionIcon, ActionIconProps, Hotkey } from '@lobehub/ui';
|
||||
import { Compass, FolderClosed, MessageSquare, Palette } from 'lucide-react';
|
||||
import { memo, useMemo, useTransition } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { INBOX_SESSION_ID } from '@/const/session';
|
||||
import { SESSION_CHAT_URL } from '@/const/url';
|
||||
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
@@ -31,11 +32,8 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
|
||||
const { t } = useTranslation('common');
|
||||
const navigate = useNavigate();
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
const [switchBackToChat, isMobile] = useGlobalStore((s) => [
|
||||
s.switchBackToChat,
|
||||
s.isMobile,
|
||||
]);
|
||||
const [, { unpinAgent }] = usePinnedAgentState();
|
||||
const [switchBackToChat, isMobile] = useGlobalStore((s) => [s.switchBackToChat, s.isMobile]);
|
||||
const { showMarket, enableKnowledgeBase, showAiImage } =
|
||||
useServerConfigStore(featureFlagsSelectors);
|
||||
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.NavigateToChat));
|
||||
@@ -69,6 +67,7 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
|
||||
e.preventDefault();
|
||||
startTransition(() => {
|
||||
switchBackToChat(activeSessionId);
|
||||
unpinAgent();
|
||||
});
|
||||
}}
|
||||
size={ICON_SIZE}
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import { useMemo } from 'react';
|
||||
'use client';
|
||||
|
||||
import { parseAsBoolean, useQueryParam } from './useQueryParam';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { useUrlHydrationStore } from '@/store/urlHydration';
|
||||
|
||||
export const usePinnedAgentState = () => {
|
||||
const [isPinned, setIsPinned] = useQueryParam('pinned', parseAsBoolean.withDefault(false), {
|
||||
clearOnDefault: true,
|
||||
});
|
||||
const isPinned = useSessionStore((s) => s.isAgentPinned);
|
||||
const setAgentPinned = useSessionStore((s) => s.setAgentPinned);
|
||||
const toggleAgentPinned = useSessionStore((s) => s.toggleAgentPinned);
|
||||
const syncToUrl = useUrlHydrationStore((s) => s.syncAgentPinnedToUrl);
|
||||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
pinAgent: () => setIsPinned(true),
|
||||
setIsPinned,
|
||||
togglePinAgent: () => setIsPinned((prev) => !prev),
|
||||
unpinAgent: () => setIsPinned(false),
|
||||
}),
|
||||
[setIsPinned],
|
||||
);
|
||||
const withSync = <T extends (...args: any[]) => void>(fn: T) => {
|
||||
return (...args: Parameters<T>) => {
|
||||
fn(...args);
|
||||
syncToUrl();
|
||||
};
|
||||
};
|
||||
|
||||
return [isPinned, actions] as const;
|
||||
return [
|
||||
isPinned,
|
||||
{
|
||||
pinAgent: withSync(() => setAgentPinned(true)),
|
||||
setIsPinned: withSync(setAgentPinned),
|
||||
togglePinAgent: withSync(toggleAgentPinned),
|
||||
unpinAgent: withSync(() => setAgentPinned(false)),
|
||||
},
|
||||
] as const;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAiInfraStore } from '@/store/aiInfra';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useServerConfigStore } from '@/store/serverConfig';
|
||||
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
||||
import { useUrlHydrationStore } from '@/store/urlHydration';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { authSelectors } from '@/store/user/selectors';
|
||||
|
||||
@@ -19,6 +20,10 @@ const StoreInitialization = memo(() => {
|
||||
// prefetch error ns to avoid don't show error content correctly
|
||||
useTranslation('error');
|
||||
|
||||
// Initialize from URL (one-time)
|
||||
const initAgentPinnedFromUrl = useUrlHydrationStore((s) => s.initAgentPinnedFromUrl);
|
||||
initAgentPinnedFromUrl();
|
||||
|
||||
const router = useRouter();
|
||||
const [isLogin, isSignedIn, useInitUserState] = useUserStore((s) => [
|
||||
authSelectors.isLogin(s),
|
||||
|
||||
@@ -76,6 +76,15 @@ export interface SessionAction {
|
||||
*/
|
||||
removeSession: (id: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Set the agent panel pinned state
|
||||
*/
|
||||
setAgentPinned: (pinned: boolean | ((prev: boolean) => boolean)) => void;
|
||||
/**
|
||||
* Toggle the agent panel pinned state
|
||||
*/
|
||||
toggleAgentPinned: () => void;
|
||||
|
||||
updateSearchKeywords: (keywords: string) => void;
|
||||
|
||||
useFetchSessions: (
|
||||
@@ -187,12 +196,26 @@ export const createSessionSlice: StateCreator<
|
||||
}
|
||||
},
|
||||
|
||||
setAgentPinned: (value) => {
|
||||
set(
|
||||
(state) => ({
|
||||
isAgentPinned: typeof value === 'function' ? value(state.isAgentPinned) : value,
|
||||
}),
|
||||
false,
|
||||
n('setAgentPinned'),
|
||||
);
|
||||
},
|
||||
|
||||
switchSession: (sessionId) => {
|
||||
if (get().activeId === sessionId) return;
|
||||
|
||||
set({ activeId: sessionId }, false, n(`activeSession/${sessionId}`));
|
||||
},
|
||||
|
||||
toggleAgentPinned: () => {
|
||||
set((state) => ({ isAgentPinned: !state.isAgentPinned }), false, n('toggleAgentPinned'));
|
||||
},
|
||||
|
||||
triggerSessionUpdate: async (id) => {
|
||||
await get().internal_updateSession(id, { updatedAt: new Date() });
|
||||
},
|
||||
|
||||
@@ -7,6 +7,11 @@ export interface SessionState {
|
||||
*/
|
||||
activeId: string;
|
||||
defaultSessions: LobeSessions;
|
||||
/**
|
||||
* @title Whether the agent panel is pinned
|
||||
* @description Controls the agent panel pinning state in the UI layout
|
||||
*/
|
||||
isAgentPinned: boolean;
|
||||
isSearching: boolean;
|
||||
isSessionsFirstFetchFinished: boolean;
|
||||
pinnedSessions: LobeSessions;
|
||||
@@ -22,6 +27,7 @@ export interface SessionState {
|
||||
export const initialSessionState: SessionState = {
|
||||
activeId: 'inbox',
|
||||
defaultSessions: [],
|
||||
isAgentPinned: false,
|
||||
isSearching: false,
|
||||
isSessionsFirstFetchFinished: false,
|
||||
pinnedSessions: [],
|
||||
|
||||
56
src/store/urlHydration/action.ts
Normal file
56
src/store/urlHydration/action.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
import type { UrlHydrationStore } from './store';
|
||||
|
||||
export interface UrlHydrationAction {
|
||||
/**
|
||||
* Initialize store state from URL (one-time on app load)
|
||||
*/
|
||||
initAgentPinnedFromUrl: () => void;
|
||||
|
||||
/**
|
||||
* Sync agent pinned state to URL (call after state change)
|
||||
*/
|
||||
syncAgentPinnedToUrl: () => void;
|
||||
}
|
||||
|
||||
export const urlHydrationAction: StateCreator<
|
||||
UrlHydrationStore,
|
||||
[['zustand/devtools', never]],
|
||||
[],
|
||||
UrlHydrationAction
|
||||
> = (set, get) => ({
|
||||
initAgentPinnedFromUrl: () => {
|
||||
if (get().isAgentPinnedInitialized) return;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const pinnedParam = params.get('pinned');
|
||||
|
||||
console.log('pinnedParam', pinnedParam);
|
||||
|
||||
if (pinnedParam === 'true') {
|
||||
useSessionStore.setState({ isAgentPinned: true });
|
||||
}
|
||||
|
||||
set({ isAgentPinnedInitialized: true });
|
||||
}
|
||||
},
|
||||
|
||||
syncAgentPinnedToUrl: () => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const isAgentPinned = useSessionStore.getState().isAgentPinned;
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
if (isAgentPinned) {
|
||||
url.searchParams.set('pinned', 'true');
|
||||
} else {
|
||||
url.searchParams.delete('pinned');
|
||||
}
|
||||
|
||||
window.history.replaceState(null, '', `${url.pathname}${url.search}`);
|
||||
},
|
||||
});
|
||||
1
src/store/urlHydration/index.ts
Normal file
1
src/store/urlHydration/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { type UrlHydrationStore,useUrlHydrationStore } from './store';
|
||||
12
src/store/urlHydration/initialState.ts
Normal file
12
src/store/urlHydration/initialState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* URL Hydration Store State
|
||||
*
|
||||
* Tracks initialization status to ensure one-time URL reading.
|
||||
*/
|
||||
export interface UrlHydrationState {
|
||||
isAgentPinnedInitialized: boolean;
|
||||
}
|
||||
|
||||
export const initialState: UrlHydrationState = {
|
||||
isAgentPinnedInitialized: false,
|
||||
};
|
||||
28
src/store/urlHydration/store.ts
Normal file
28
src/store/urlHydration/store.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { subscribeWithSelector } from 'zustand/middleware';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { createWithEqualityFn } from 'zustand/traditional';
|
||||
import { StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import { createDevtools } from '../middleware/createDevtools';
|
||||
import { type UrlHydrationAction, urlHydrationAction } from './action';
|
||||
import { type UrlHydrationState, initialState } from './initialState';
|
||||
|
||||
// =============== 聚合 createStoreFn ============ //
|
||||
|
||||
export interface UrlHydrationStore extends UrlHydrationState, UrlHydrationAction {}
|
||||
|
||||
const createStore: StateCreator<UrlHydrationStore, [['zustand/devtools', never]]> = (
|
||||
...parameters
|
||||
) => ({
|
||||
...initialState,
|
||||
...urlHydrationAction(...parameters),
|
||||
});
|
||||
|
||||
// =============== 实装 useStore ============ //
|
||||
|
||||
const devtools = createDevtools('urlHydration');
|
||||
|
||||
export const useUrlHydrationStore = createWithEqualityFn<UrlHydrationStore>()(
|
||||
subscribeWithSelector(devtools(createStore)),
|
||||
shallow,
|
||||
);
|
||||
Reference in New Issue
Block a user