feat: 实现话题删除功能

This commit is contained in:
arvinxx
2023-07-30 18:20:25 +08:00
parent fdbe08489e
commit 970889defc
9 changed files with 107 additions and 27 deletions

View File

@@ -6,5 +6,6 @@ config.rules['unicorn/no-negated-condition'] = 0;
config.rules['unicorn/prefer-type-error'] = 0;
config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
config.rules['unicorn/no-null'] = 0;
config.rules['unicorn/no-typeof-undefined'] = 0;
module.exports = config;

View File

@@ -1,3 +1,5 @@
export const LOADING_FLAT = '...';
// 只要 start with 这个,就可以判断为 function message
export const FUNCTION_MESSAGE_FLAG = '{"function';

View File

@@ -52,6 +52,7 @@ export default {
temp: '临时',
tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
topic: {
confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
saveCurrentMessages: '将当前会话保存为话题',
searchPlaceholder: '搜索话题...',
},

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Icon } from '@lobehub/ui';
import { Dropdown, type MenuProps, Tag, Typography } from 'antd';
import { App, Dropdown, type MenuProps, Tag, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { MessageSquareDashed, MoreVertical, PencilLine, Share2, Star, Trash } from 'lucide-react';
import { memo, useMemo, useState } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
@@ -52,17 +52,17 @@ export interface ConfigCellProps {
}
const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const { styles, theme, cx } = useStyles();
const { t } = useTranslation('common');
const [dispatchTopic, toggleTopic] = useSessionStore(
(s) => [s.dispatchTopic, s.toggleTopic],
const [dispatchTopic, toggleTopic, removeTopic] = useSessionStore(
(s) => [s.dispatchTopic, s.toggleTopic, s.removeTopic],
shallow,
);
const { modal } = App.useApp();
// TODO: 动作绑定
const items: MenuProps['items'] = useMemo(
const items = useMemo<MenuProps['items']>(
() => [
{
icon: <Icon icon={PencilLine} />,
@@ -75,9 +75,22 @@ const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) =>
label: t('share'),
},
{
danger: true,
icon: <Icon icon={Trash} />,
key: 'delete',
label: t('delete'),
onClick: () => {
if (!id) return;
modal.confirm({
centered: true,
okButtonProps: { danger: true },
onOk: () => {
removeTopic(id);
},
title: t('topic.confirmRemoveTopic'),
});
},
},
],
[],
@@ -105,7 +118,7 @@ const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) =>
icon={Star}
onClick={() => {
if (!id) return;
dispatchTopic({ id, key: 'favorite', type: 'updateChatTopic', value: !fav });
dispatchTopic({ favorite: !fav, id, type: 'favorChatTopic' });
}}
size={'small'}
/>
@@ -115,20 +128,27 @@ const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) =>
</Paragraph>
{id ? '' : <Tag>{t('temp')}</Tag>}
</Flexbox>
<Dropdown
arrow={false}
menu={{ items }}
onOpenChange={setDropdownOpen}
open={dropdownOpen}
trigger={['click']}
>
<ActionIcon
className="topic-more"
icon={MoreVertical}
size={'small'}
style={dropdownOpen ? { opacity: 1 } : {}}
/>
</Dropdown>
{!id ? null : (
<Dropdown
arrow={false}
menu={{
items,
onClick: ({ domEvent }) => {
domEvent.stopPropagation();
},
}}
trigger={['click']}
>
<ActionIcon
className="topic-more"
icon={MoreVertical}
onClick={(e) => {
e.stopPropagation();
}}
size={'small'}
/>
</Dropdown>
)}
</Flexbox>
);
});

View File

@@ -1,5 +1,6 @@
import { StateCreator } from 'zustand/vanilla';
import { LOADING_FLAT } from '@/const/message';
import { fetchChatModel } from '@/services/chatModel';
import { fetchPlugin } from '@/services/plugin';
import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
@@ -10,8 +11,6 @@ import { nanoid } from '@/utils/uuid';
import { MessageDispatch, messagesReducer } from '../reducers/message';
const LOADING_FLAT = '...';
/**
* 聊天操作
*/

View File

@@ -1,7 +1,8 @@
import { StateCreator } from 'zustand/vanilla';
import { LOADING_FLAT } from '@/const/message';
import { promptSummaryTitle } from '@/prompts/chat';
import { SessionStore, chatSelectors, sessionSelectors } from '@/store/session';
import { SessionStore, chatSelectors, sessionSelectors, topicSelectors } from '@/store/session';
import { fetchPresetTaskResult } from '@/utils/fetch';
import { nanoid } from '@/utils/uuid';
@@ -13,6 +14,11 @@ export interface ChatTopicAction {
* @param payload - 要分发的主题
*/
dispatchTopic: (payload: ChatTopicDispatch) => void;
/**
* 移除话题
* @param id
*/
removeTopic: (id: string) => void;
/**
* 将当前消息保存为主题
*/
@@ -28,6 +34,7 @@ export interface ChatTopicAction {
*/
updateTopicLoading: (id?: string) => void;
}
export const chatTopic: StateCreator<
SessionStore,
[['zustand/devtools', never]],
@@ -43,6 +50,24 @@ export const chatTopic: StateCreator<
get().dispatchSession({ id: activeId, topics, type: 'updateSessionTopic' });
},
removeTopic: (id) => {
const { dispatchTopic, dispatchMessage, toggleTopic } = get();
// 移除关联的 message
const messages = topicSelectors.getTopicMessages(id)(get());
for (const m of messages) {
dispatchMessage({
id: m.id,
type: 'deleteMessage',
});
}
// 最后移除 topic
dispatchTopic({ id, type: 'deleteChatTopic' });
// 切换到默认 topic
toggleTopic();
},
saveToTopic: () => {
const session = sessionSelectors.currentSession(get());
if (!session) return;
@@ -71,6 +96,8 @@ export const chatTopic: StateCreator<
dispatchMessage({ id: m.id, key: 'topicId', type: 'updateMessage', value: topicId });
}
dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: LOADING_FLAT });
let output = '';
// 自动总结话题标题
@@ -91,7 +118,6 @@ export const chatTopic: StateCreator<
toggleTopic: (id) => {
set({ activeTopicId: id });
},
updateTopicLoading: (id) => {
set({ topicLoadingId: id });
},

View File

@@ -14,12 +14,22 @@ interface UpdateChatTopicAction {
value: any;
}
interface FavorChatTopicAction {
favorite: boolean;
id: string;
type: 'favorChatTopic';
}
interface DeleteChatTopicAction {
id: string;
type: 'deleteChatTopic';
}
export type ChatTopicDispatch = AddChatTopicAction | UpdateChatTopicAction | DeleteChatTopicAction;
export type ChatTopicDispatch =
| AddChatTopicAction
| UpdateChatTopicAction
| DeleteChatTopicAction
| FavorChatTopicAction;
export const topicReducer = (state: ChatTopicMap, payload: ChatTopicDispatch): ChatTopicMap => {
switch (payload.type) {
@@ -46,6 +56,18 @@ export const topicReducer = (state: ChatTopicMap, payload: ChatTopicDispatch): C
});
}
case 'favorChatTopic': {
return produce(state, (draftState) => {
const { favorite, id } = payload;
if (!draftState[id]) return;
const topic = draftState[id];
topic.favorite = favorite;
});
}
case 'deleteChatTopic': {
return produce(state, (draftState) => {
delete draftState[payload.id];

View File

@@ -1,6 +1,6 @@
import { currentChats } from './chat';
import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
import { currentTopics } from './topic';
import { currentTopics, getTopicMessages } from './topic';
export const chatSelectors = {
chatsTokenCount,
@@ -11,4 +11,5 @@ export const chatSelectors = {
export const topicSelectors = {
currentTopics,
getTopicMessages,
};

View File

@@ -1,5 +1,6 @@
// 展示在聊天框中的消息
import { SessionStore, sessionSelectors } from '@/store/session';
import { organizeChats } from '@/store/session/slices/chat/selectors/utils';
import { ChatTopic } from '@/types/topic';
export const currentTopics = (s: SessionStore): ChatTopic[] => {
@@ -14,3 +15,10 @@ export const currentTopics = (s: SessionStore): ChatTopic[] => {
return [...favTopics, ...defaultTopics];
};
export const getTopicMessages = (topicId: string) => (s: SessionStore) => {
const session = sessionSelectors.currentSession(s);
if (!session) return [];
return organizeChats(session, { assistant: '', user: '' }, topicId);
};