refactor(ChatProvider, useChat): implement context for chat state management and enhance event handling

- Introduced ChatProvider to manage active assistant and topic state using React context.
- Updated useChat hook to utilize context for accessing chat state and actions.
- Refactored message navigation to emit events for setting active assistant and topic.
- Improved topic selection logic to ensure active topic is valid when topics change.
- Integrated ChatProvider into HomePage and HistoryPage for consistent chat state management.
This commit is contained in:
kangfenmao 2025-06-13 18:56:55 +08:00
parent 58f3edb352
commit cdfa2ac13a
14 changed files with 215 additions and 161 deletions

View File

@ -1,5 +1,5 @@
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, easeInOut, motion } from 'framer-motion'
import { useEffect } from 'react' import { useEffect } from 'react'
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom' import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
@ -94,9 +94,9 @@ const PageContainer = styled(motion.div)`
` `
const pageTransition = { const pageTransition = {
type: 'tween', type: 'tween' as const,
duration: 0.25, duration: 0.25,
ease: [0.4, 0.0, 0.2, 1] ease: easeInOut
} }
const pageVariants = { const pageVariants = {

View File

@ -1,22 +1,43 @@
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setActiveAssistant, setActiveTopic } from '@renderer/store/runtime'
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { Topic } from '@renderer/types' import { Topic } from '@renderer/types'
import { useEffect } from 'react' import { use, useEffect, useMemo, useState } from 'react'
import { createContext } from 'react'
import { useAssistants, useTopicsForAssistant } from './useAssistant' import { useTopicsForAssistant } from './useAssistant'
import { useSettings } from './useSettings' import { useSettings } from './useSettings'
export const useChat = () => { interface ChatContextType {
const { assistants } = useAssistants() activeAssistant: Assistant
const activeAssistant = useAppSelector((state) => state.runtime.chat.activeAssistant) || assistants[0] activeTopic: Topic
setActiveAssistant: (assistant: Assistant) => void
setActiveTopic: (topic: Topic) => void
}
const ChatContext = createContext<ChatContextType | null>(null)
export const ChatProvider = ({ children }) => {
const assistants = useAppSelector((state) => state.assistants.assistants)
const [activeAssistant, setActiveAssistant] = useState<Assistant>(assistants[0])
const topics = useTopicsForAssistant(activeAssistant.id) const topics = useTopicsForAssistant(activeAssistant.id)
const activeTopic = useAppSelector((state) => state.runtime.chat.activeTopic) || topics[0] const [activeTopic, setActiveTopic] = useState<Topic>(topics[0])
const { clickAssistantToShowTopic } = useSettings() const { clickAssistantToShowTopic } = useSettings()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
console.log('activeAssistant', activeAssistant)
console.log('activeTopic', activeTopic)
// 当 topics 变化时,如果当前 activeTopic 不在 topics 中,设置第一个 topic
useEffect(() => {
if (!topics.find((topic) => topic.id === activeTopic?.id)) {
const firstTopic = topics[0]
firstTopic && setActiveTopic(firstTopic)
}
}, [topics, activeTopic?.id])
// 当 activeTopic 变化时加载消息
useEffect(() => { useEffect(() => {
if (activeTopic) { if (activeTopic) {
dispatch(loadTopicMessagesThunk(activeTopic.id)) dispatch(loadTopicMessagesThunk(activeTopic.id))
@ -24,28 +45,38 @@ export const useChat = () => {
} }
}, [activeTopic, dispatch]) }, [activeTopic, dispatch])
useEffect(() => { // 处理点击助手显示话题侧边栏
if (topics.find((topic) => topic.id === activeTopic?.id)) {
return
}
const firstTopic = topics[0]
firstTopic && dispatch(setActiveTopic(firstTopic))
}, [activeAssistant, activeTopic?.id, dispatch, topics])
useEffect(() => { useEffect(() => {
if (clickAssistantToShowTopic) { if (clickAssistantToShowTopic) {
EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR) EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)
} }
}, [clickAssistantToShowTopic, activeAssistant]) }, [clickAssistantToShowTopic, activeAssistant])
return { useEffect(() => {
activeAssistant, const subscriptions = [
activeTopic, EventEmitter.on(EVENT_NAMES.SET_ASSISTANT, setActiveAssistant),
setActiveAssistant: (assistant: Assistant) => { EventEmitter.on(EVENT_NAMES.SET_TOPIC, setActiveTopic)
dispatch(setActiveAssistant(assistant)) ]
}, return () => subscriptions.forEach((subscription) => subscription())
setActiveTopic: (topic: Topic) => { }, [])
dispatch(setActiveTopic(topic))
} const value = useMemo(
} () => ({
activeAssistant,
activeTopic,
setActiveAssistant,
setActiveTopic
}),
[activeAssistant, activeTopic]
)
return <ChatContext value={value}>{children}</ChatContext>
}
export const useChat = () => {
const context = use(ChatContext)
if (!context) {
throw new Error('useChat must be used within ChatProvider')
}
return context
} }

View File

@ -3,7 +3,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { RootState } from '@renderer/store' import { RootState } from '@renderer/store'
import { messageBlocksSelectors } from '@renderer/store/messageBlock' import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { selectMessagesForTopic } from '@renderer/store/newMessage' import { selectMessagesForTopic } from '@renderer/store/newMessage'
import { setActiveTopic, setSelectedMessageIds, toggleMultiSelectMode } from '@renderer/store/runtime' import { setSelectedMessageIds, toggleMultiSelectMode } from '@renderer/store/runtime'
import { Topic } from '@renderer/types' import { Topic } from '@renderer/types'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -27,10 +27,6 @@ export const useChatContext = (activeTopic: Topic) => {
return () => unsubscribe() return () => unsubscribe()
}, [dispatch]) }, [dispatch])
useEffect(() => {
dispatch(setActiveTopic(activeTopic))
}, [dispatch, activeTopic])
const handleToggleMultiSelectMode = useCallback( const handleToggleMultiSelectMode = useCallback(
(value: boolean) => { (value: boolean) => {
dispatch(toggleMultiSelectMode(value)) dispatch(toggleMultiSelectMode(value))

View File

@ -29,7 +29,9 @@ const App: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
const handleClick = () => { const handleClick = () => {
if (!isHome) { if (!isHome) {
navigate('/') setTimeout(() => {
navigate('/')
}, 300)
} }
openMinappKeepAlive(app) openMinappKeepAlive(app)

View File

@ -1,4 +1,5 @@
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { ChatProvider } from '@renderer/hooks/useChat'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
import { Topic } from '@renderer/types' import { Topic } from '@renderer/types'
@ -72,51 +73,53 @@ const TopicsPage: FC = () => {
}, []) }, [])
return ( return (
<Container> <ChatProvider>
<HStack style={{ padding: '0 12px', marginTop: 8 }}> <Container>
<Input <HStack style={{ padding: '0 12px', marginTop: 8 }}>
prefix={ <Input
stack.length > 1 ? ( prefix={
<SearchIcon className="back-icon" onClick={goBack}> stack.length > 1 ? (
<ChevronLeft size={16} /> <SearchIcon className="back-icon" onClick={goBack}>
</SearchIcon> <ChevronLeft size={16} />
) : ( </SearchIcon>
<SearchIcon> ) : (
<Search size={15} /> <SearchIcon>
</SearchIcon> <Search size={15} />
) </SearchIcon>
} )
suffix={search.length >= 2 ? <CornerDownLeft size={16} /> : null} }
ref={inputRef} suffix={search.length >= 2 ? <CornerDownLeft size={16} /> : null}
placeholder={t('history.search.placeholder')} ref={inputRef}
value={search} placeholder={t('history.search.placeholder')}
onChange={(e) => setSearch(e.target.value.trimStart())} value={search}
allowClear onChange={(e) => setSearch(e.target.value.trimStart())}
autoFocus allowClear
spellCheck={false} autoFocus
style={{ paddingLeft: 0 }} spellCheck={false}
variant="borderless" style={{ paddingLeft: 0 }}
size="middle" variant="borderless"
onPressEnter={onSearch} size="middle"
/> onPressEnter={onSearch}
</HStack> />
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} /> </HStack>
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
<TopicsHistory <TopicsHistory
keywords={search} keywords={search}
onClick={onTopicClick as any} onClick={onTopicClick as any}
onSearch={onSearch} onSearch={onSearch}
style={{ display: isShow('topics') }} style={{ display: isShow('topics') }}
/> />
<TopicMessages topic={topic} style={{ display: isShow('topic') }} /> <TopicMessages topic={topic} style={{ display: isShow('topic') }} />
<SearchResults <SearchResults
keywords={isShow('search') ? searchKeywords : ''} keywords={isShow('search') ? searchKeywords : ''}
onMessageClick={onMessageClick} onMessageClick={onMessageClick}
onTopicClick={onTopicClick} onTopicClick={onTopicClick}
style={{ display: isShow('search') }} style={{ display: isShow('search') }}
/> />
<SearchMessage message={message} style={{ display: isShow('message') }} /> <SearchMessage message={message} style={{ display: isShow('message') }} />
</Container> </Container>
</ChatProvider>
) )
} }

View File

@ -1,7 +1,6 @@
import { ArrowRightOutlined } from '@ant-design/icons' import { ArrowRightOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext' import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
import { useChat } from '@renderer/hooks/useChat'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { getTopicById } from '@renderer/hooks/useTopic' import { getTopicById } from '@renderer/hooks/useTopic'
import { default as MessageItem } from '@renderer/pages/home/Messages/Message' import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
@ -22,7 +21,6 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
const { messageStyle } = useSettings() const { messageStyle } = useSettings()
const { t } = useTranslation() const { t } = useTranslation()
const [topic, setTopic] = useState<Topic | null>(null) const [topic, setTopic] = useState<Topic | null>(null)
const { setActiveAssistant, setActiveTopic } = useChat()
useEffect(() => { useEffect(() => {
runAsyncFunction(async () => { runAsyncFunction(async () => {
@ -50,13 +48,11 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
type="text" type="text"
size="middle" size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }} style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }}
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })} onClick={() => locateToMessage(message)}
icon={<ArrowRightOutlined />} icon={<ArrowRightOutlined />}
/> />
<HStack mt="10px" justifyContent="center"> <HStack mt="10px" justifyContent="center">
<Button <Button onClick={() => locateToMessage(message)} icon={<ArrowRightOutlined />}>
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })}
icon={<ArrowRightOutlined />}>
{t('history.locate.message')} {t('history.locate.message')}
</Button> </Button>
</HStack> </HStack>

View File

@ -59,7 +59,7 @@ const TopicMessages: FC<Props> = ({ topic, ...props }) => {
type="text" type="text"
size="middle" size="middle"
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }} style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }}
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })} onClick={() => locateToMessage(message)}
icon={<ArrowRightOutlined />} icon={<ArrowRightOutlined />}
/> />
<Divider style={{ margin: '8px auto 15px' }} variant="dashed" /> <Divider style={{ margin: '8px auto 15px' }} variant="dashed" />

View File

@ -1,4 +1,5 @@
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { ChatProvider } from '@renderer/hooks/useChat'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { FC, useEffect } from 'react' import { FC, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -19,15 +20,17 @@ const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => {
}, [showAssistants, showTopics, topicPosition]) }, [showAssistants, showTopics, topicPosition])
return ( return (
<HStack style={{ display: 'flex', flex: 1 }} id="home-page"> <ChatProvider>
<MainSidebar /> <HStack style={{ display: 'flex', flex: 1 }} id="home-page">
<Container style={style}> <MainSidebar />
<ChatNavbar /> <Container style={style}>
<ContentContainer id="content-container"> <ChatNavbar />
<Chat /> <ContentContainer id="content-container">
</ContentContainer> <Chat />
</Container> </ContentContainer>
</HStack> </Container>
</HStack>
</ChatProvider>
) )
} }

View File

@ -52,7 +52,6 @@ import {
SubMenu SubMenu
} from './MainSidebarStyles' } from './MainSidebarStyles'
import OpenedMinappTabs from './OpenedMinapps' import OpenedMinappTabs from './OpenedMinapps'
import PinnedApps from './PinnedApps'
type Tab = 'assistants' | 'topic' type Tab = 'assistants' | 'topic'
@ -174,7 +173,7 @@ const MainSidebar: FC = () => {
overflow: showAssistants ? 'initial' : 'hidden' overflow: showAssistants ? 'initial' : 'hidden'
}}> }}>
<MainNavbar /> <MainNavbar />
<MainMenu> <MainMenu style={{ marginBottom: 4 }}>
<MainMenuItem active={isAppMenuExpanded} onClick={() => setIsAppMenuExpanded(!isAppMenuExpanded)}> <MainMenuItem active={isAppMenuExpanded} onClick={() => setIsAppMenuExpanded(!isAppMenuExpanded)}>
<MainMenuItemLeft> <MainMenuItemLeft>
<MainMenuItemIcon> <MainMenuItemIcon>
@ -200,7 +199,6 @@ const MainSidebar: FC = () => {
</MainMenuItemLeft> </MainMenuItemLeft>
</MainMenuItem> </MainMenuItem>
))} ))}
<PinnedApps />
</SubMenu> </SubMenu>
)} )}
<OpenedMinappTabs /> <OpenedMinappTabs />
@ -299,9 +297,7 @@ const MainContainer = styled.div`
` `
const AssistantContainer = styled.div` const AssistantContainer = styled.div`
margin: 0 10px; margin: 4px 10px;
margin-top: 4px;
margin-bottom: 4px;
display: flex; display: flex;
` `

View File

@ -85,7 +85,6 @@ export const TabsContainer = styled.div`
export const TabsWrapper = styled(Scrollbar as any)` export const TabsWrapper = styled(Scrollbar as any)`
width: 100%; width: 100%;
padding: 5px 0;
max-height: 50vh; max-height: 50vh;
` `

View File

@ -1,14 +1,16 @@
import DragableList from '@renderer/components/DragableList'
import MinAppIcon from '@renderer/components/Icons/MinAppIcon' import MinAppIcon from '@renderer/components/Icons/MinAppIcon'
import IndicatorLight from '@renderer/components/IndicatorLight' import IndicatorLight from '@renderer/components/IndicatorLight'
import { Center } from '@renderer/components/Layout' import { Center } from '@renderer/components/Layout'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Empty } from 'antd' import { Empty } from 'antd'
import { Dropdown } from 'antd' import { Dropdown } from 'antd'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { FC, useEffect } from 'react' import { FC, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -18,7 +20,6 @@ import {
MainMenuItemLeft, MainMenuItemLeft,
MainMenuItemRight, MainMenuItemRight,
MainMenuItemText, MainMenuItemText,
Menus,
TabsContainer, TabsContainer,
TabsWrapper TabsWrapper
} from './MainSidebarStyles' } from './MainSidebarStyles'
@ -27,8 +28,24 @@ const OpenedMinapps: FC = () => {
const { minappShow, openedKeepAliveMinapps, currentMinappId } = useRuntime() const { minappShow, openedKeepAliveMinapps, currentMinappId } = useRuntime()
const { openMinappKeepAlive, hideMinappPopup, closeMinapp, closeAllMinapps } = useMinappPopup() const { openMinappKeepAlive, hideMinappPopup, closeMinapp, closeAllMinapps } = useMinappPopup()
const { showOpenedMinappsInSidebar } = useSettings() const { showOpenedMinappsInSidebar } = useSettings()
const { pinned, updatePinnedMinapps } = useMinapps()
const { t } = useTranslation() const { t } = useTranslation()
// 合并并排序应用列表
const sortedApps = useMemo(() => {
// 分离已打开但未固定的应用
const openedNotPinned = openedKeepAliveMinapps.filter((app) => !pinned.find((p) => p.id === app.id))
// 获取固定应用列表(保持原有顺序)
const pinnedApps = pinned.map((app) => {
const openedApp = openedKeepAliveMinapps.find((o) => o.id === app.id)
return openedApp || app
})
// 把已启动但未固定的放到列表下面
return [...pinnedApps, ...openedNotPinned]
}, [openedKeepAliveMinapps, pinned])
const handleOnClick = (app) => { const handleOnClick = (app) => {
if (minappShow && currentMinappId === app.id) { if (minappShow && currentMinappId === app.id) {
hideMinappPopup() hideMinappPopup()
@ -59,51 +76,80 @@ const OpenedMinapps: FC = () => {
container.style.setProperty('--indicator-right', `${indicatorRight}px`) container.style.setProperty('--indicator-right', `${indicatorRight}px`)
}, [currentMinappId, openedKeepAliveMinapps, minappShow]) }, [currentMinappId, openedKeepAliveMinapps, minappShow])
const isShowOpened = showOpenedMinappsInSidebar && openedKeepAliveMinapps.length > 0 const isShowApps = showOpenedMinappsInSidebar && sortedApps.length > 0
if (!isShowOpened) return <TabsContainer className="TabsContainer" /> if (!isShowApps) return <TabsContainer className="TabsContainer" />
return ( return (
<TabsContainer className="TabsContainer"> <TabsContainer className="TabsContainer">
<Divider /> <Divider />
<TabsWrapper> <TabsWrapper>
<Menus> <DragableList
{openedKeepAliveMinapps.map((app) => { list={sortedApps}
onUpdate={(newList) => {
// 只更新固定应用的顺序
const newPinned = newList.filter((app) => pinned.find((p) => p.id === app.id))
updatePinnedMinapps(newPinned)
}}
listStyle={{ margin: '4px 0' }}>
{(app) => {
const isPinned = pinned.find((p) => p.id === app.id)
const isOpened = openedKeepAliveMinapps.find((o) => o.id === app.id)
const menuItems: MenuProps['items'] = [ const menuItems: MenuProps['items'] = [
{ {
key: 'closeApp', key: 'togglePin',
label: t('minapp.sidebar.close.title'), label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.pin.title'),
onClick: () => closeMinapp(app.id) onClick: () => {
}, if (isPinned) {
{ const newPinned = pinned.filter((item) => item.id !== app.id)
key: 'closeAllApp', updatePinnedMinapps(newPinned)
label: t('minapp.sidebar.closeall.title'), } else {
onClick: () => closeAllMinapps() updatePinnedMinapps([...pinned, app])
}
}
} }
] ]
if (isOpened) {
menuItems.push(
{
key: 'closeApp',
label: t('minapp.sidebar.close.title'),
onClick: () => closeMinapp(app.id)
},
{
key: 'closeAllApp',
label: t('minapp.sidebar.closeall.title'),
onClick: () => closeAllMinapps()
}
)
}
return ( return (
<MainMenuItem key={app.id} onClick={() => handleOnClick(app)}> <Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}>
<MainMenuItemLeft> <MainMenuItem key={app.id} onClick={() => handleOnClick(app)}>
<MainMenuItemIcon> <MainMenuItemLeft>
<MinAppIcon size={22} app={app} style={{ borderRadius: 6 }} sidebar /> <MainMenuItemIcon>
</MainMenuItemIcon> <MinAppIcon size={22} app={app} style={{ borderRadius: 6 }} sidebar />
<MainMenuItemText>{app.name}</MainMenuItemText> </MainMenuItemIcon>
</MainMenuItemLeft> <MainMenuItemText>{app.name}</MainMenuItemText>
<MainMenuItemRight style={{ marginRight: 4 }}> </MainMenuItemLeft>
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}> {isOpened && (
<IndicatorLight color="var(--color-primary)" shadow={false} animation={false} size={5} /> <MainMenuItemRight style={{ marginRight: 4 }}>
</Dropdown> <IndicatorLight color="var(--color-primary)" shadow={false} animation={false} size={5} />
</MainMenuItemRight> </MainMenuItemRight>
</MainMenuItem> )}
</MainMenuItem>
</Dropdown>
) )
})} }}
{isEmpty(openedKeepAliveMinapps) && ( </DragableList>
<Center> {isEmpty(sortedApps) && (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Center>
</Center> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)} </Center>
</Menus> )}
</TabsWrapper> </TabsWrapper>
<Divider /> <Divider />
</TabsContainer> </TabsContainer>

View File

@ -29,5 +29,7 @@ export const EVENT_NAMES = {
SHOW_MODEL_SELECTOR: 'SHOW_MODEL_SELECTOR', SHOW_MODEL_SELECTOR: 'SHOW_MODEL_SELECTOR',
EDIT_CODE_BLOCK: 'EDIT_CODE_BLOCK', EDIT_CODE_BLOCK: 'EDIT_CODE_BLOCK',
CHANGE_TOPIC: 'CHANGE_TOPIC', CHANGE_TOPIC: 'CHANGE_TOPIC',
OPEN_MINAPP: 'OPEN_MINAPP' OPEN_MINAPP: 'OPEN_MINAPP',
SET_ASSISTANT: 'SET_ASSISTANT',
SET_TOPIC: 'SET_TOPIC'
} }

View File

@ -81,15 +81,7 @@ export function isGenerating() {
}) })
} }
export async function locateToMessage({ export async function locateToMessage(message: Message) {
message,
setActiveAssistant,
setActiveTopic
}: {
message: Message
setActiveAssistant: (assistant: Assistant) => void
setActiveTopic: (topic: Topic) => void
}) {
await isGenerating() await isGenerating()
SearchPopup.hide() SearchPopup.hide()
@ -100,11 +92,11 @@ export async function locateToMessage({
return return
} }
setActiveAssistant(assistant) EventEmitter.emit(EVENT_NAMES.SET_ASSISTANT, assistant)
setActiveTopic(topic) EventEmitter.emit(EVENT_NAMES.SET_TOPIC, topic)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0) setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 500) setTimeout(() => EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id), 200)
} }
/** /**

View File

@ -1,13 +1,11 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { AppLogo, UserAvatar } from '@renderer/config/env' import { AppLogo, UserAvatar } from '@renderer/config/env'
import type { Assistant, MinAppType, Topic } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import type { UpdateInfo } from 'builder-util-runtime' import type { UpdateInfo } from 'builder-util-runtime'
export interface ChatState { export interface ChatState {
isMultiSelectMode: boolean isMultiSelectMode: boolean
selectedMessageIds: string[] selectedMessageIds: string[]
activeTopic: Topic | null
activeAssistant: Assistant | null
/** topic ids that are currently being renamed */ /** topic ids that are currently being renamed */
renamingTopics: string[] renamingTopics: string[]
/** topic ids that are newly renamed */ /** topic ids that are newly renamed */
@ -70,8 +68,6 @@ const initialState: RuntimeState = {
chat: { chat: {
isMultiSelectMode: false, isMultiSelectMode: false,
selectedMessageIds: [], selectedMessageIds: [],
activeTopic: null,
activeAssistant: null,
renamingTopics: [], renamingTopics: [],
newlyRenamedTopics: [] newlyRenamedTopics: []
} }
@ -124,12 +120,6 @@ const runtimeSlice = createSlice({
setSelectedMessageIds: (state, action: PayloadAction<string[]>) => { setSelectedMessageIds: (state, action: PayloadAction<string[]>) => {
state.chat.selectedMessageIds = action.payload state.chat.selectedMessageIds = action.payload
}, },
setActiveTopic: (state, action: PayloadAction<Topic>) => {
state.chat.activeTopic = action.payload
},
setActiveAssistant: (state, action: PayloadAction<Assistant>) => {
state.chat.activeAssistant = action.payload
},
setRenamingTopics: (state, action: PayloadAction<string[]>) => { setRenamingTopics: (state, action: PayloadAction<string[]>) => {
state.chat.renamingTopics = action.payload state.chat.renamingTopics = action.payload
}, },
@ -154,8 +144,6 @@ export const {
// Chat related actions // Chat related actions
toggleMultiSelectMode, toggleMultiSelectMode,
setSelectedMessageIds, setSelectedMessageIds,
setActiveTopic,
setActiveAssistant,
setRenamingTopics, setRenamingTopics,
setNewlyRenamedTopics setNewlyRenamedTopics
} = runtimeSlice.actions } = runtimeSlice.actions