Skip to content

Commit

Permalink
✨ feat: experimentally support to pin assistant to sidebar (lobehub#4514
Browse files Browse the repository at this point in the history
)

* ✨ feat: support pin assistant at side

* ✨ feat: add a feature flag

* βœ… test: fix tests

* πŸ’„ style: fix avtar meta

* πŸ’„ style: fix pin mode style

* πŸ’„ style: add hotkeys
  • Loading branch information
arvinxx authored Oct 27, 2024
1 parent ef9e395 commit 6e55865
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 5 deletions.
91 changes: 91 additions & 0 deletions src/app/(main)/@nav/_layout/Desktop/PinList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Avatar, Tooltip } from '@lobehub/ui';
import { Divider } from 'antd';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { useHotkeys } from 'react-hotkeys-hook';
import { Flexbox } from 'react-layout-kit';

import HotKeys from '@/components/HotKeys';
import { useSessionStore } from '@/store/session';
import { sessionHelpers } from '@/store/session/helpers';
import { sessionSelectors } from '@/store/session/selectors';

const useStyles = createStyles(({ css, token }) => ({
avatar: css`
position: relative;
transition: all 200ms ease-out 0s;
&:hover {
box-shadow: 0 0 0 2px ${token.colorPrimary};
}
`,
avatarActive: css`
background: ${token.colorFillQuaternary};
box-shadow: 0 0 0 2px ${token.colorPrimaryBorder};
`,
}));

const PinList = () => {
const { styles, cx } = useStyles();
const list = useSessionStore(sessionSelectors.pinnedSessions, isEqual);
const [activeId, switchSession] = useSessionStore((s) => [s.activeId, s.switchSession]);

const hasList = list.length > 0;
const [isPinned, setPinned] = useQueryState('pinned', parseAsBoolean);

const switchAgent = (id: string) => {
switchSession(id);
setPinned(true);
};

useHotkeys(
list.slice(0, 9).map((e, i) => `ctrl+${i + 1}`),
(keyboardEvent, hotkeysEvent) => {
if (!hotkeysEvent.keys?.[0]) return;

const index = parseInt(hotkeysEvent.keys?.[0]) - 1;
const item = list[index];
if (!item) return;

switchAgent(item.id);
},
{ enableOnFormTags: true, preventDefault: true },
);

return (
hasList && (
<>
<Divider style={{ margin: '8px 12px' }} />
<Flexbox flex={1} gap={12} height={'100%'}>
{list.slice(0, 9).map((item, index) => (
<Tooltip
key={item.id}
placement={'right'}
title={
<Flexbox gap={8} horizontal>
{sessionHelpers.getTitle(item.meta)}
<HotKeys inverseTheme keys={`ctrl+${index + 1}`} />
</Flexbox>
}
>
<Avatar
avatar={sessionHelpers.getAvatar(item.meta)}
background={item.meta.backgroundColor}
className={cx(
styles.avatar,
isPinned && activeId === item.id ? styles.avatarActive : undefined,
)}
onClick={() => {
switchAgent(item.id);
}}
/>
</Tooltip>
))}
</Flexbox>
</>
)
);
};

export default PinList;
11 changes: 9 additions & 2 deletions src/app/(main)/@nav/_layout/Desktop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@ import { memo } from 'react';
import { useActiveTabKey } from '@/hooks/useActiveTabKey';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';

import Avatar from './Avatar';
import BottomActions from './BottomActions';
import PinList from './PinList';
import TopActions from './TopActions';

const Nav = memo(() => {
const sidebarKey = useActiveTabKey();
const inZenMode = useGlobalStore(systemStatusSelectors.inZenMode);

const { showPinList } = useServerConfigStore(featureFlagsSelectors);
return (
!inZenMode && (
<SideNav
avatar={<Avatar />}
bottomActions={<BottomActions />}
style={{ height: '100%', zIndex: 100 }}
topActions={<TopActions tab={sidebarKey} />}
topActions={
<>
<TopActions tab={sidebarKey} />
{showPinList && <PinList />}
</>
}
/>
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { ActionIcon, Avatar, ChatHeaderTitle } from '@lobehub/ui';
import { Skeleton } from 'antd';
import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { Suspense, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
Expand All @@ -21,6 +22,7 @@ const Main = memo(() => {
const { t } = useTranslation('chat');

useInitAgentConfig();
const [isPinned] = useQueryState('pinned', parseAsBoolean);

const [init, isInbox, title, description, avatar, backgroundColor] = useSessionStore((s) => [
sessionSelectors.isSomeSessionActive(s),
Expand Down Expand Up @@ -49,7 +51,7 @@ const Main = memo(() => {
</Flexbox>
) : (
<Flexbox align={'center'} gap={4} horizontal>
{
{!isPinned && (
<ActionIcon
aria-label={t('agents')}
icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
Expand All @@ -62,7 +64,7 @@ const Main = memo(() => {
size={DESKTOP_HEADER_ICON_SIZE}
title={t('agents')}
/>
}
)}
<Avatar
avatar={avatar}
background={backgroundColor}
Expand Down
7 changes: 6 additions & 1 deletion src/app/(main)/chat/_layout/Desktop/SessionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
import { createStyles, useResponsive } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { PropsWithChildren, memo, useEffect, useState } from 'react';

import { FOLDER_WIDTH } from '@/const/layoutTokens';
Expand All @@ -20,6 +21,8 @@ export const useStyles = createStyles(({ css, token }) => ({
const SessionPanel = memo<PropsWithChildren>(({ children }) => {
const { md = true } = useResponsive();

const [isPinned] = useQueryState('pinned', parseAsBoolean);

const { styles } = useStyles();
const [sessionsWidth, sessionExpandable, updatePreference] = useGlobalStore((s) => [
systemStatusSelectors.sessionWidth(s),
Expand Down Expand Up @@ -56,7 +59,9 @@ const SessionPanel = memo<PropsWithChildren>(({ children }) => {
<DraggablePanel
className={styles.panel}
defaultSize={{ width: tmpWidth }}
expand={sessionExpandable}
// 当进ε…₯ pin ζ¨‘εΌδΈ‹οΌŒδΈε―ε±•εΌ€
expand={!isPinned && sessionExpandable}
expandable={!isPinned}
maxWidth={400}
minWidth={FOLDER_WIDTH}
mode={md ? 'fixed' : 'float'}
Expand Down
3 changes: 3 additions & 0 deletions src/config/featureFlags/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const FeatureFlagsSchema = z.object({
*/
webrtc_sync: z.boolean().optional(),
check_updates: z.boolean().optional(),
pin_list: z.boolean().optional(),

// settings
language_model_settings: z.boolean().optional(),
Expand Down Expand Up @@ -45,6 +46,7 @@ export type IFeatureFlags = z.infer<typeof FeatureFlagsSchema>;

export const DEFAULT_FEATURE_FLAGS: IFeatureFlags = {
webrtc_sync: false,
pin_list: false,

language_model_settings: true,

Expand Down Expand Up @@ -85,6 +87,7 @@ export const mapFeatureFlagsEnvToState = (config: IFeatureFlags) => {

showCreateSession: config.create_session,
showLLM: config.language_model_settings,
showPinList: config.pin_list,

showOpenAIApiKey: config.openai_api_key,
showOpenAIProxyUrl: config.openai_proxy_url,
Expand Down
1 change: 1 addition & 0 deletions src/store/serverConfig/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('featureFlagsSelectors', () => {
showWelcomeSuggest: true,
enableClerkSignUp: true,
showMarket: true,
showPinList: false,
enableSTT: true,
});
});
Expand Down
9 changes: 9 additions & 0 deletions src/store/session/slices/session/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { t } from 'i18next';

import { DEFAULT_AVATAR } from '@/const/meta';
import { DEFAULT_AGENT_LOBE_SESSION } from '@/const/session';
import { MetaData } from '@/types/meta';
import { LobeAgentSession, LobeSessions } from '@/types/session';

export const getSessionPinned = (session: LobeAgentSession) => session.pinned;

const getAvatar = (s: MetaData) => s.avatar || DEFAULT_AVATAR;
const getTitle = (s: MetaData) => s.title || t('defaultSession', { ns: 'common' });

const getSessionById = (id: string, sessions: LobeSessions): LobeAgentSession => {
const session = sessions.find((s) => s.id === id);

Expand All @@ -12,6 +19,8 @@ const getSessionById = (id: string, sessions: LobeSessions): LobeAgentSession =>
};

export const sessionHelpers = {
getAvatar,
getSessionById,
getSessionPinned,
getTitle,
};

0 comments on commit 6e55865

Please sign in to comment.