mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
✨ feat: 实现话题删除功能
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const LOADING_FLAT = '...';
|
||||
|
||||
// 只要 start with 这个,就可以判断为 function message
|
||||
export const FUNCTION_MESSAGE_FLAG = '{"function';
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export default {
|
||||
temp: '临时',
|
||||
tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
|
||||
topic: {
|
||||
confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
|
||||
saveCurrentMessages: '将当前会话保存为话题',
|
||||
searchPlaceholder: '搜索话题...',
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 = '...';
|
||||
|
||||
/**
|
||||
* 聊天操作
|
||||
*/
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user