Skip to content

Commit

Permalink
Merge pull request #43 from lobehub/history-count
Browse files Browse the repository at this point in the history
限制历史记录条数
  • Loading branch information
arvinxx authored Aug 1, 2023
2 parents aefc2fa + 7baa022 commit 6d0e5de
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 65 deletions.
3 changes: 3 additions & 0 deletions src/components/SliderWithInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
controls,
style,
className,
disabled,
...props
}) => {
const handleOnchange = useCallback((value: number | null) => {
Expand All @@ -38,6 +39,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
>
<Slider
defaultValue={defaultValue}
disabled={disabled}
max={max}
min={min}
onChange={handleOnchange}
Expand All @@ -50,6 +52,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
<InputNumber
controls={size !== 'small' || controls}
defaultValue={defaultValue}
disabled={disabled}
max={max}
min={min}
onChange={handleOnchange}
Expand Down
5 changes: 4 additions & 1 deletion src/locales/default/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export default {
title: '是否开启历史消息长度压缩阈值',
},
enableHistoryCount: {
title: '是否开启携带的历史消息数限制',
alias: '不限制',
limited: '只包含 {{number}} 条会话消息',
title: '限制历史消息数',
unlimited: '不限历史消息数',
},
historyCount: {
desc: '每次请求携带的历史消息数',
Expand Down
3 changes: 1 addition & 2 deletions src/pages/chat/SessionList/List/SessionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
meta.backgroundColor,
session?.updateAt,
session.config.model,
chatSelectors.currentChats(s).length,
chatSelectors.getChatsById(id)(s).length,
s.removeSession,
s.pinSession,
];
Expand Down Expand Up @@ -177,7 +177,6 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
pin={pin}
ref={ref}
showAction={open || isHovering}
style={{ color: theme.colorText }}
title={title}
/>
);
Expand Down
1 change: 0 additions & 1 deletion src/pages/chat/SessionList/List/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const useStyles = createStyles(({ css, token }) => {
`,
container: css`
position: relative;
height: 72px;
`,

modalRoot: css`
Expand Down
80 changes: 46 additions & 34 deletions src/pages/chat/[id]/Conversation/Input/Action.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ActionIcon } from '@lobehub/ui';
import { Dropdown, Popconfirm, Popover } from 'antd';
import { BrainCog, Eraser, Thermometer } from 'lucide-react';
import { Dropdown, Popover, Switch } from 'antd';
import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';

import SliderWithInput from '@/components/SliderWithInput';
Expand All @@ -11,16 +12,15 @@ import { LanguageModel } from '@/types/llm';

const InputActions = memo(() => {
const { t } = useTranslation('setting');
const [clearMessage, updateAgentConfig] = useSessionStore(
(s) => [s.clearMessage, s.updateAgentConfig],
shallow,
);
const [model, temperature] = useSessionStore((s) => {

const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfig(s);
return [
config.model,
config.params.temperature,
// config.historyCount
config.historyCount,
!config.enableHistoryCount,
s.updateAgentConfig,
];
}, shallow);

Expand Down Expand Up @@ -54,6 +54,7 @@ const InputActions = memo(() => {
value={temperature}
/>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
Expand All @@ -62,36 +63,47 @@ const InputActions = memo(() => {
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
/>
</Popover>
{/*TODO 历史记录功能实现 */}
{/*<Popover*/}
{/* content={*/}
{/* <SliderWithInput*/}
{/* min={0}*/}
{/* onChange={(v) => {*/}
{/* updateAgentConfig({ historyCount: v });*/}
{/* }}*/}
{/* step={1}*/}
{/* style={{ width: 120 }}*/}
{/* value={historyCount}*/}
{/* />*/}
{/* }*/}
{/* trigger={'click'}*/}
{/*>*/}
{/* <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />*/}
{/*</Popover>*/}
<Popconfirm
cancelText={t('cancel', { ns: 'common' })}
okButtonProps={{ danger: true }}
okText={t('ok', { ns: 'common' })}
onConfirm={() => clearMessage()}
title={t('confirmClearCurrentMessages', { ns: 'common' })}
<Popover
arrow={false}
content={
<Flexbox align={'center'} gap={16} horizontal>
<SliderWithInput
disabled={unlimited}
max={30}
min={1}
onChange={(v) => {
updateAgentConfig({ historyCount: v });
}}
step={1}
style={{ width: 160 }}
value={historyCount}
/>
<Flexbox align={'center'} gap={4} horizontal>
<Switch
checked={unlimited}
onChange={(checked) => {
updateAgentConfig({ enableHistoryCount: !checked });
}}
size={'small'}
/>
{t('settingChat.enableHistoryCount.alias')}
</Flexbox>
</Flexbox>
}
placement={'top'}
trigger={'click'}
>
<ActionIcon
icon={Eraser}
icon={unlimited ? TimerOff : Timer}
placement={'bottom'}
title={t('clearCurrentMessages', { ns: 'common' })}
title={t(
unlimited
? 'settingChat.enableHistoryCount.unlimited'
: 'settingChat.enableHistoryCount.limited',
{ number: historyCount || 0 },
)}
/>
</Popconfirm>
</Popover>
</>
);
});
Expand Down
31 changes: 31 additions & 0 deletions src/pages/chat/[id]/Conversation/Input/ActionRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ActionIcon } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { Eraser } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';

import { useSessionStore } from '@/store/session';

const ActionsRight = memo(() => {
const { t } = useTranslation('setting');
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig], shallow);

return (
<Popconfirm
cancelText={t('cancel', { ns: 'common' })}
okButtonProps={{ danger: true }}
okText={t('ok', { ns: 'common' })}
onConfirm={() => clearMessage()}
title={t('confirmClearCurrentMessages', { ns: 'common' })}
>
<ActionIcon
icon={Eraser}
placement={'bottom'}
title={t('clearCurrentMessages', { ns: 'common' })}
/>
</Popconfirm>
);
});

export default ActionsRight;
2 changes: 2 additions & 0 deletions src/pages/chat/[id]/Conversation/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useSessionStore } from '@/store/session';
import { useSettings } from '@/store/settings';

import InputActions from './Action';
import ActionsRight from './ActionRight';
import Token from './Token';

const ChatInput = () => {
Expand Down Expand Up @@ -52,6 +53,7 @@ const ChatInput = () => {
<Token input={text} />
</>
}
actionsRight={<ActionsRight />}
expand={expand}
footer={footer}
minHeight={CHAT_TEXTAREA_HEIGHT}
Expand Down
21 changes: 12 additions & 9 deletions src/store/session/slices/chat/actions/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LOADING_FLAT } from '@/const/message';
import { fetchChatModel } from '@/services/chatModel';
import { fetchPlugin } from '@/services/plugin';
import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
import { getSlicedMessagesWithConfig } from '@/store/session/slices/chat/utils';
import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
import { fetchSSE } from '@/utils/fetch';
import { isFunctionMessage } from '@/utils/message';
Expand Down Expand Up @@ -123,12 +124,17 @@ export const chatMessage: StateCreator<

const compiler = template(config.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });

// 对 message 做统一预处理
// ========================== //
// 对 messages 做统一预处理 //
// ========================== //

// 1. 替换 inputMessage 模板
// 1. 按参数设定截断长度
const slicedMessages = getSlicedMessagesWithConfig(messages, config);

// 2. 替换 inputMessage 模板
const postMessages = !config.inputTemplate
? messages
: messages.map((m) => {
? slicedMessages
: slicedMessages.map((m) => {
if (m.role === 'user') {
try {
return { ...m, content: compiler({ text: m.content }) };
Expand All @@ -141,12 +147,9 @@ export const chatMessage: StateCreator<
return m;
});

// 2. TODO 按参数设定截断长度

// 3. 添加 systemRole
const { systemRole } = agentSelectors.currentAgentConfig(get());
if (systemRole) {
postMessages.unshift({ content: systemRole, role: 'system' } as ChatMessage);
if (config.systemRole) {
postMessages.unshift({ content: config.systemRole, role: 'system' } as ChatMessage);
}

const fetcher = () =>
Expand Down
41 changes: 26 additions & 15 deletions src/store/session/slices/chat/selectors/chat.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import { DEFAULT_USER_AVATAR } from '@/const/meta';
import { agentSelectors } from '@/store/session';
import { useSettings } from '@/store/settings';
import { ChatMessage } from '@/types/chatMessage';

import type { SessionStore } from '../../../store';
import { agentSelectors } from '../../agentConfig';
import { sessionSelectors } from '../../session';
import { getSlicedMessagesWithConfig } from '../utils';
import { organizeChats } from './utils';

// 展示在聊天框中的消息
export const getChatsById =
(id: string) =>
(s: SessionStore): ChatMessage[] => {
const session = sessionSelectors.getSessionById(id)(s);

if (!session) return [];

return organizeChats(
session,
{
assistant: agentSelectors.currentAgentAvatar(s),
user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
},
s.activeTopicId,
);
};

// 当前激活的消息列表
export const currentChats = (s: SessionStore): ChatMessage[] => {
const session = sessionSelectors.currentSession(s);
if (!session) return [];

return organizeChats(
session,
{
assistant: agentSelectors.currentAgentAvatar(s),
user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
},
s.activeTopicId,
);
if (!s.activeId) return [];

return getChatsById(s.activeId)(s);
};

export const systemRoleSel = (s: SessionStore): string => {
export const currentChatsWithHistoryConfig = (s: SessionStore): ChatMessage[] => {
const chats = currentChats(s);
const config = agentSelectors.currentAgentConfig(s);

return config.systemRole;
return getSlicedMessagesWithConfig(chats, config);
};
3 changes: 2 additions & 1 deletion src/store/session/slices/chat/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { currentChats } from './chat';
import { currentChats, getChatsById } from './chat';
import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
import { currentTopics, getTopicMessages } from './topic';

export const chatSelectors = {
chatsTokenCount,
currentChats,
getChatsById,
systemRoleTokenCount,
totalTokenCount,
};
Expand Down
12 changes: 10 additions & 2 deletions src/store/session/slices/chat/selectors/token.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { encode } from 'gpt-tokenizer';

import { agentSelectors } from '@/store/session';

import type { SessionStore } from '../../../store';
import { currentChats, systemRoleSel } from './chat';
import { currentChatsWithHistoryConfig } from './chat';

export const systemRoleSel = (s: SessionStore): string => {
const config = agentSelectors.currentAgentConfig(s);

return config.systemRole;
};

const systemRoleTokens = (s: SessionStore): number[] => {
const systemRole = systemRoleSel(s);
Expand All @@ -10,7 +18,7 @@ const systemRoleTokens = (s: SessionStore): number[] => {
};

const chatsTokens = (s: SessionStore): number[] => {
const chats = currentChats(s);
const chats = currentChatsWithHistoryConfig(s);
return encode(chats.map((m) => m.content).join(''));
};

Expand Down
13 changes: 13 additions & 0 deletions src/store/session/slices/chat/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ChatMessage } from '@/types/chatMessage';
import { LobeAgentConfig } from '@/types/session';

export const getSlicedMessagesWithConfig = (
messages: ChatMessage[],
config: LobeAgentConfig,
): ChatMessage[] => {
// 如果没有开启历史消息数限制,或者限制为 0,则直接返回
if (!config.enableHistoryCount || !config.historyCount) return messages;

// 如果开启了,则返回尾部的N条消息
return messages.reverse().slice(0, config.historyCount).reverse();
};

0 comments on commit 6d0e5de

Please sign in to comment.