refactor: migrate chat context to a custom hook and enhance multi-selection functionality

- Replaced the ChatContext with a custom hook `useChatContext` for better modularity and reusability.
- Updated components to utilize the new hook, passing the active topic as an argument.
- Enhanced multi-selection logic and state management for messages, improving user experience in the chat interface.
- Removed the old ChatContext file to streamline the codebase.
This commit is contained in:
kangfenmao 2025-05-21 21:40:27 +08:00
parent 46ae4f9b55
commit b1839b722f
11 changed files with 286 additions and 274 deletions

View File

@ -1,13 +1,19 @@
import { useChatContext } from '@renderer/pages/home/Messages/ChatContext'
import { useChatContext } from '@renderer/hooks/useChatContext'
import { Topic } from '@renderer/types'
import { Button, Tooltip } from 'antd'
import { Copy, Save, Trash, X } from 'lucide-react'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const MultiSelectActionPopup: FC = () => {
interface Props {
topic: Topic
}
const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
const { t } = useTranslation()
const { toggleMultiSelectMode, selectedMessageIds, isMultiSelectMode, handleMultiSelectAction } = useChatContext()
const { toggleMultiSelectMode, selectedMessageIds, isMultiSelectMode, handleMultiSelectAction } =
useChatContext(topic)
const handleAction = (action: string) => {
handleMultiSelectAction(action, selectedMessageIds)

View File

@ -0,0 +1,185 @@
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { RootState } from '@renderer/store'
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { selectMessagesForTopic } from '@renderer/store/newMessage'
import { setActiveTopic, setSelectedMessageIds, toggleMultiSelectMode } from '@renderer/store/runtime'
import { Topic } from '@renderer/types'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector, useStore } from 'react-redux'
export const useChatContext = (activeTopic: Topic) => {
const { t } = useTranslation()
const dispatch = useDispatch()
const store = useStore<RootState>()
const { deleteMessage } = useMessageOperations(activeTopic)
const [messageRefs, setMessageRefs] = useState<Map<string, HTMLElement>>(new Map())
const isMultiSelectMode = useSelector((state: RootState) => state.runtime.chat.isMultiSelectMode)
const selectedMessageIds = useSelector((state: RootState) => state.runtime.chat.selectedMessageIds)
useEffect(() => {
const unsubscribe = EventEmitter.on(EVENT_NAMES.CHANGE_TOPIC, () => {
dispatch(toggleMultiSelectMode(false))
})
return () => unsubscribe()
}, [dispatch])
useEffect(() => {
dispatch(setActiveTopic(activeTopic))
}, [dispatch, activeTopic])
const handleToggleMultiSelectMode = useCallback(
(value: boolean) => {
dispatch(toggleMultiSelectMode(value))
},
[dispatch]
)
const registerMessageElement = useCallback((id: string, element: HTMLElement | null) => {
setMessageRefs((prev) => {
const newRefs = new Map(prev)
if (element) {
newRefs.set(id, element)
} else {
newRefs.delete(id)
}
return newRefs
})
}, [])
const locateMessage = useCallback(
(messageId: string) => {
const messageElement = messageRefs.get(messageId)
if (messageElement) {
// 检查消息是否可见
const display = window.getComputedStyle(messageElement).display
if (display === 'none') {
// 如果消息隐藏,需要处理显示逻辑
// 查找消息并设置为选中状态
const state = store.getState()
const messages = selectMessagesForTopic(state, activeTopic.id)
const message = messages.find((m) => m.id === messageId)
if (message) {
// 这里需要实现设置消息为选中状态的逻辑
// 可能需要调用其他函数或修改状态
}
}
// 滚动到消息位置
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
},
[messageRefs, store, activeTopic.id]
)
const handleSelectMessage = useCallback(
(messageId: string, selected: boolean) => {
dispatch(
setSelectedMessageIds(
selected ? [...selectedMessageIds, messageId] : selectedMessageIds.filter((id) => id !== messageId)
)
)
},
[dispatch, selectedMessageIds]
)
const handleMultiSelectAction = useCallback(
async (actionType: string, messageIds: string[]) => {
if (messageIds.length === 0) {
window.message.warning(t('chat.multiple.select.empty'))
return
}
const state = store.getState()
const messages = selectMessagesForTopic(state, activeTopic.id)
const messageBlocks = messageBlocksSelectors.selectEntities(state)
switch (actionType) {
case 'delete':
window.modal.confirm({
title: t('message.delete.confirm.title'),
content: t('message.delete.confirm.content', { count: messageIds.length }),
okButtonProps: { danger: true },
centered: true,
onOk: async () => {
try {
await Promise.all(messageIds.map((messageId) => deleteMessage(messageId)))
window.message.success(t('message.delete.success'))
handleToggleMultiSelectMode(false)
} catch (error) {
console.error('Failed to delete messages:', error)
window.message.error(t('message.delete.failed'))
}
}
})
break
case 'save': {
const assistantMessages = messages.filter((msg) => messageIds.includes(msg.id))
if (assistantMessages.length > 0) {
const contentToSave = assistantMessages
.map((msg) => {
return msg.blocks
.map((blockId) => {
const block = messageBlocks[blockId]
return block && 'content' in block ? block.content : ''
})
.filter(Boolean)
.join('\n')
.trim()
})
.join('\n\n---\n\n')
const fileName = `chat_export_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.md`
await window.api.file.save(fileName, contentToSave)
window.message.success({ content: t('message.save.success.title'), key: 'save-messages' })
handleToggleMultiSelectMode(false)
} else {
window.message.warning(t('message.save.no.assistant'))
}
break
}
case 'copy': {
const assistantMessages = messages.filter((msg) => messageIds.includes(msg.id))
if (assistantMessages.length > 0) {
const contentToCopy = assistantMessages
.map((msg) => {
return msg.blocks
.map((blockId) => {
const block = messageBlocks[blockId]
return block && 'content' in block ? block.content : ''
})
.filter(Boolean)
.join('\n')
.trim()
})
.join('\n\n---\n\n')
navigator.clipboard.writeText(contentToCopy)
window.message.success({ content: t('message.copied'), key: 'copy-messages' })
handleToggleMultiSelectMode(false)
} else {
window.message.warning(t('message.copy.no.assistant'))
}
break
}
default:
break
}
},
[t, store, activeTopic.id, deleteMessage, handleToggleMultiSelectMode]
)
return {
isMultiSelectMode,
selectedMessageIds,
toggleMultiSelectMode: handleToggleMultiSelectMode,
handleMultiSelectAction,
handleSelectMessage,
activeTopic,
locateMessage,
messageRefs,
registerMessageElement
}
}

View File

@ -3,7 +3,6 @@ import { HStack } from '@renderer/components/Layout'
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
import { useSettings } from '@renderer/hooks/useSettings'
import { getTopicById } from '@renderer/hooks/useTopic'
import { ChatProvider } from '@renderer/pages/home/Messages/ChatContext'
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
import { locateToMessage } from '@renderer/services/MessagesService'
import NavigationService from '@renderer/services/NavigationService'
@ -43,27 +42,25 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
}
return (
<ChatProvider activeTopic={topic}>
<MessageEditingProvider>
<MessagesContainer {...props} className={messageStyle}>
<ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}>
<MessageItem message={message} topic={topic} hideMenuBar={true} />
<Button
type="text"
size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }}
onClick={() => locateToMessage(navigate, message)}
icon={<ArrowRightOutlined />}
/>
<HStack mt="10px" justifyContent="center">
<Button onClick={() => locateToMessage(navigate, message)} icon={<ArrowRightOutlined />}>
{t('history.locate.message')}
</Button>
</HStack>
</ContainerWrapper>
</MessagesContainer>
</MessageEditingProvider>
</ChatProvider>
<MessageEditingProvider>
<MessagesContainer {...props} className={messageStyle}>
<ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}>
<MessageItem message={message} topic={topic} hideMenuBar={true} />
<Button
type="text"
size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }}
onClick={() => locateToMessage(navigate, message)}
icon={<ArrowRightOutlined />}
/>
<HStack mt="10px" justifyContent="center">
<Button onClick={() => locateToMessage(navigate, message)} icon={<ArrowRightOutlined />}>
{t('history.locate.message')}
</Button>
</HStack>
</ContainerWrapper>
</MessagesContainer>
</MessageEditingProvider>
)
}

View File

@ -4,7 +4,6 @@ import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
import useScrollPosition from '@renderer/hooks/useScrollPosition'
import { useSettings } from '@renderer/hooks/useSettings'
import { ChatProvider } from '@renderer/pages/home/Messages/ChatContext'
import { getAssistantById } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { isGenerating, locateToMessage } from '@renderer/services/MessagesService'
@ -48,35 +47,33 @@ const TopicMessages: FC<Props> = ({ topic, ...props }) => {
}
return (
<ChatProvider activeTopic={topic}>
<MessageEditingProvider>
<MessagesContainer {...props} ref={containerRef} onScroll={handleScroll} className={messageStyle}>
<ContainerWrapper style={{ paddingTop: 30, paddingBottom: 30 }}>
{topic?.messages.map((message) => (
<div key={message.id} style={{ position: 'relative' }}>
<MessageItem message={message} topic={topic} hideMenuBar={true} />
<Button
type="text"
size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }}
onClick={() => locateToMessage(navigate, message)}
icon={<ArrowRightOutlined />}
/>
<Divider style={{ margin: '8px auto 15px' }} variant="dashed" />
</div>
))}
{isEmpty && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
{!isEmpty && (
<HStack justifyContent="center">
<Button onClick={() => onContinueChat(topic)} icon={<MessageOutlined />}>
{t('history.continue_chat')}
</Button>
</HStack>
)}
</ContainerWrapper>
</MessagesContainer>
</MessageEditingProvider>
</ChatProvider>
<MessageEditingProvider>
<MessagesContainer {...props} ref={containerRef} onScroll={handleScroll} className={messageStyle}>
<ContainerWrapper style={{ paddingTop: 30, paddingBottom: 30 }}>
{topic?.messages.map((message) => (
<div key={message.id} style={{ position: 'relative' }}>
<MessageItem message={message} topic={topic} hideMenuBar={true} />
<Button
type="text"
size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }}
onClick={() => locateToMessage(navigate, message)}
icon={<ArrowRightOutlined />}
/>
<Divider style={{ margin: '8px auto 15px' }} variant="dashed" />
</div>
))}
{isEmpty && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
{!isEmpty && (
<HStack justifyContent="center">
<Button onClick={() => onContinueChat(topic)} icon={<MessageOutlined />}>
{t('history.continue_chat')}
</Button>
</HStack>
)}
</ContainerWrapper>
</MessagesContainer>
</MessageEditingProvider>
)
}

View File

@ -2,6 +2,7 @@ import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSea
import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup'
import { QuickPanelProvider } from '@renderer/components/QuickPanel'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useChatContext } from '@renderer/hooks/useChatContext'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowTopics } from '@renderer/hooks/useStore'
@ -13,7 +14,6 @@ import { useHotkeys } from 'react-hotkeys-hook'
import styled from 'styled-components'
import Inputbar from './Inputbar/Inputbar'
import { ChatProvider, useChatContext } from './Messages/ChatContext'
import Messages from './Messages/Messages'
import Tabs from './Tabs'
@ -28,7 +28,7 @@ const ChatContent: FC<Props> = (props) => {
const { assistant } = useAssistant(props.assistant.id)
const { topicPosition, messageStyle, showAssistants } = useSettings()
const { showTopics } = useShowTopics()
const { isMultiSelectMode } = useChatContext()
const { isMultiSelectMode } = useChatContext(props.activeTopic)
const mainRef = React.useRef<HTMLDivElement>(null)
const contentSearchRef = React.useRef<ContentSearchRef>(null)
@ -127,7 +127,7 @@ const ChatContent: FC<Props> = (props) => {
</MessagesContainer>
<QuickPanelProvider>
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
{isMultiSelectMode && <MultiSelectActionPopup />}
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
</QuickPanelProvider>
</Main>
{topicPosition === 'right' && showTopics && (
@ -144,11 +144,7 @@ const ChatContent: FC<Props> = (props) => {
}
const Chat: FC<Props> = (props) => {
return (
<ChatProvider activeTopic={props.activeTopic}>
<ChatContent {...props} />
</ChatProvider>
)
return <ChatContent {...props} />
}
const MessagesContainer = styled.div`

View File

@ -1,200 +0,0 @@
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { RootState } from '@renderer/store'
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { selectMessagesForTopic } from '@renderer/store/newMessage'
import { Topic } from '@renderer/types'
import { createContext, FC, ReactNode, use, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore } from 'react-redux'
interface ChatContextProps {
isMultiSelectMode: boolean
selectedMessageIds: string[]
toggleMultiSelectMode: (value: boolean) => void
handleMultiSelectAction: (actionType: string, messageIds: string[]) => void
handleSelectMessage: (messageId: string, selected: boolean) => void
activeTopic: Topic
locateMessage: (messageId: string) => void
messageRefs: Map<string, HTMLElement>
registerMessageElement: (id: string, element: HTMLElement | null) => void
}
interface ChatProviderProps {
children: ReactNode
activeTopic: Topic
}
const ChatContext = createContext<ChatContextProps | undefined>(undefined)
export const useChatContext = () => {
const context = use(ChatContext)
if (!context) {
throw new Error('useChatContext 必须在 ChatProvider 内使用')
}
return context
}
export const ChatProvider: FC<ChatProviderProps> = ({ children, activeTopic }) => {
const { t } = useTranslation()
const { deleteMessage } = useMessageOperations(activeTopic)
const [isMultiSelectMode, setIsMultiSelectMode] = useState(false)
const [selectedMessageIds, setSelectedMessageIds] = useState<string[]>([])
const [messageRefs, setMessageRefs] = useState<Map<string, HTMLElement>>(new Map())
const store = useStore<RootState>()
useEffect(() => {
const unsubscribe = EventEmitter.on(EVENT_NAMES.CHANGE_TOPIC, () => setIsMultiSelectMode(false))
return () => unsubscribe()
}, [])
const toggleMultiSelectMode = (value: boolean) => {
setIsMultiSelectMode(value)
if (!value) {
setSelectedMessageIds([])
}
}
const registerMessageElement = useCallback((id: string, element: HTMLElement | null) => {
setMessageRefs((prev) => {
const newRefs = new Map(prev)
if (element) {
newRefs.set(id, element)
} else {
newRefs.delete(id)
}
return newRefs
})
}, [])
const locateMessage = (messageId: string) => {
const messageElement = messageRefs.get(messageId)
if (messageElement) {
// 检查消息是否可见
const display = window.getComputedStyle(messageElement).display
if (display === 'none') {
// 如果消息隐藏,需要处理显示逻辑
// 查找消息并设置为选中状态
const state = store.getState()
const messages = selectMessagesForTopic(state, activeTopic.id)
const message = messages.find((m) => m.id === messageId)
if (message) {
// 这里需要实现设置消息为选中状态的逻辑
// 可能需要调用其他函数或修改状态
}
}
// 滚动到消息位置
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
const handleSelectMessage = (messageId: string, selected: boolean) => {
setSelectedMessageIds((prev) => {
const newSet = new Set(prev)
if (selected) {
newSet.add(messageId)
} else {
newSet.delete(messageId)
}
return Array.from(newSet)
})
}
const handleMultiSelectAction = (actionType: string, messageIds: string[]) => {
if (messageIds.length === 0) {
window.message.warning(t('chat.multiple.select.empty'))
return
}
const state = store.getState()
const messages = selectMessagesForTopic(state, activeTopic.id)
const messageBlocks = messageBlocksSelectors.selectEntities(state)
switch (actionType) {
case 'delete':
window.modal.confirm({
title: t('message.delete.confirm.title'),
content: t('message.delete.confirm.content', { count: messageIds.length }),
okButtonProps: { danger: true },
centered: true,
onOk: async () => {
try {
await Promise.all(messageIds.map((messageId) => deleteMessage(messageId)))
window.message.success(t('message.delete.success'))
toggleMultiSelectMode(false)
} catch (error) {
console.error('Failed to delete messages:', error)
window.message.error(t('message.delete.failed'))
}
}
})
break
case 'save': {
const assistantMessages = messages.filter((msg) => messageIds.includes(msg.id))
if (assistantMessages.length > 0) {
const contentToSave = assistantMessages
.map((msg) => {
return msg.blocks
.map((blockId) => {
const block = messageBlocks[blockId]
return block && 'content' in block ? block.content : ''
})
.filter(Boolean)
.join('\n')
.trim()
})
.join('\n\n---\n\n')
const fileName = `chat_export_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.md`
window.api.file.save(fileName, contentToSave)
window.message.success({ content: t('message.save.success.title'), key: 'save-messages' })
toggleMultiSelectMode(false)
} else {
window.message.warning(t('message.save.no.assistant'))
}
break
}
case 'copy': {
const assistantMessages = messages.filter((msg) => messageIds.includes(msg.id))
if (assistantMessages.length > 0) {
const contentToCopy = assistantMessages
.map((msg) => {
return msg.blocks
.map((blockId) => {
const block = messageBlocks[blockId]
return block && 'content' in block ? block.content : ''
})
.filter(Boolean)
.join('\n')
.trim()
})
.join('\n\n---\n\n')
navigator.clipboard.writeText(contentToCopy)
window.message.success({ content: t('message.copied'), key: 'copy-messages' })
toggleMultiSelectMode(false)
} else {
window.message.warning(t('message.copy.no.assistant'))
}
break
}
default:
break
}
}
const value = {
isMultiSelectMode,
selectedMessageIds,
toggleMultiSelectMode,
handleMultiSelectAction,
handleSelectMessage,
activeTopic,
locateMessage,
messageRefs,
registerMessageElement
}
return <ChatContext value={value}>{children}</ChatContext>
}

View File

@ -162,6 +162,7 @@ const MessageGroup = ({ messages, topic, hidePresetMessages, registerMessageElem
<SelectableMessage
key={`selectable-${message.id}`}
messageId={message.id}
topic={topic}
isClearMessage={message.type === 'clear'}>
{messageContent}
</SelectableMessage>

View File

@ -3,6 +3,7 @@ import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { TranslateLanguageOptions } from '@renderer/config/translate'
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
import { useChatContext } from '@renderer/hooks/useChatContext'
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getMessageTitle } from '@renderer/services/MessagesService'
@ -33,8 +34,6 @@ import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import styled from 'styled-components'
import { useChatContext } from './ChatContext'
interface Props {
message: Message
assistant: Assistant
@ -52,7 +51,7 @@ const MessageMenubar: FC<Props> = (props) => {
const { message, index, isGrouped, isLastMessage, isAssistantMessage, assistant, topic, model, messageContainerRef } =
props
const { t } = useTranslation()
const { toggleMultiSelectMode } = useChatContext()
const { toggleMultiSelectMode } = useChatContext(props.topic)
const [copied, setCopied] = useState(false)
const [isTranslating, setIsTranslating] = useState(false)
const [showRegenerateTooltip, setShowRegenerateTooltip] = useState(false)

View File

@ -1,23 +1,24 @@
import { useChatContext } from '@renderer/hooks/useChatContext'
import { Topic } from '@renderer/types'
import { Checkbox } from 'antd'
import { FC, ReactNode, useEffect, useRef } from 'react'
import styled from 'styled-components'
import { useChatContext } from './ChatContext'
interface SelectableMessageProps {
children: ReactNode
messageId: string
topic: Topic
isClearMessage?: boolean
}
const SelectableMessage: FC<SelectableMessageProps> = ({ children, messageId, isClearMessage = false }) => {
const SelectableMessage: FC<SelectableMessageProps> = ({ children, messageId, topic, isClearMessage = false }) => {
const containerRef = useRef<HTMLDivElement>(null)
const {
registerMessageElement: contextRegister,
isMultiSelectMode,
selectedMessageIds,
handleSelectMessage
} = useChatContext()
} = useChatContext(topic)
const isSelected = selectedMessageIds?.includes(messageId)

View File

@ -2,6 +2,7 @@ import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import Scrollbar from '@renderer/components/Scrollbar'
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useChatContext } from '@renderer/hooks/useChatContext'
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
import useScrollPosition from '@renderer/hooks/useScrollPosition'
import { useSettings } from '@renderer/hooks/useSettings'
@ -32,7 +33,6 @@ import { useTranslation } from 'react-i18next'
import InfiniteScroll from 'react-infinite-scroll-component'
import styled from 'styled-components'
import { useChatContext } from './ChatContext'
import ChatNavigation from './ChatNavigation'
import MessageAnchorLine from './MessageAnchorLine'
import MessageGroup from './MessageGroup'
@ -53,7 +53,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
)
const { t } = useTranslation()
const { showPrompt, showTopics, topicPosition, showAssistants, messageNavigation } = useSettings()
const { isMultiSelectMode, handleSelectMessage } = useChatContext()
const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
const { updateTopic, addTopic } = useAssistant(assistant.id)
const dispatch = useAppDispatch()
const [displayMessages, setDisplayMessages] = useState<Message[]>([])

View File

@ -1,7 +1,14 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { AppLogo, UserAvatar } from '@renderer/config/env'
import type { MinAppType } from '@renderer/types'
import type { MinAppType, Topic } from '@renderer/types'
import type { UpdateInfo } from 'builder-util-runtime'
export interface ChatState {
isMultiSelectMode: boolean
selectedMessageIds: string[]
activeTopic: Topic | null
}
export interface UpdateState {
info: UpdateInfo | null
checking: boolean
@ -27,6 +34,7 @@ export interface RuntimeState {
resourcesPath: string
update: UpdateState
export: ExportState
chat: ChatState
}
export interface ExportState {
@ -53,6 +61,11 @@ const initialState: RuntimeState = {
},
export: {
isExporting: false
},
chat: {
isMultiSelectMode: false,
selectedMessageIds: [],
activeTopic: null
}
}
@ -92,6 +105,19 @@ const runtimeSlice = createSlice({
},
setExportState: (state, action: PayloadAction<Partial<ExportState>>) => {
state.export = { ...state.export, ...action.payload }
},
// Chat related actions
toggleMultiSelectMode: (state, action: PayloadAction<boolean>) => {
state.chat.isMultiSelectMode = action.payload
if (!action.payload) {
state.chat.selectedMessageIds = []
}
},
setSelectedMessageIds: (state, action: PayloadAction<string[]>) => {
state.chat.selectedMessageIds = action.payload
},
setActiveTopic: (state, action: PayloadAction<Topic>) => {
state.chat.activeTopic = action.payload
}
}
})
@ -107,7 +133,11 @@ export const {
setFilesPath,
setResourcesPath,
setUpdateState,
setExportState
setExportState,
// Chat related actions
toggleMultiSelectMode,
setSelectedMessageIds,
setActiveTopic
} = runtimeSlice.actions
export default runtimeSlice.reducer