mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 20:12:38 +08:00
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:
parent
58f3edb352
commit
cdfa2ac13a
@ -1,5 +1,5 @@
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { AnimatePresence, easeInOut, motion } from 'framer-motion'
|
||||
import { useEffect } from 'react'
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
@ -94,9 +94,9 @@ const PageContainer = styled(motion.div)`
|
||||
`
|
||||
|
||||
const pageTransition = {
|
||||
type: 'tween',
|
||||
type: 'tween' as const,
|
||||
duration: 0.25,
|
||||
ease: [0.4, 0.0, 0.2, 1]
|
||||
ease: easeInOut
|
||||
}
|
||||
|
||||
const pageVariants = {
|
||||
|
||||
@ -1,22 +1,43 @@
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setActiveAssistant, setActiveTopic } from '@renderer/store/runtime'
|
||||
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
|
||||
import { Assistant } 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'
|
||||
|
||||
export const useChat = () => {
|
||||
const { assistants } = useAssistants()
|
||||
const activeAssistant = useAppSelector((state) => state.runtime.chat.activeAssistant) || assistants[0]
|
||||
interface ChatContextType {
|
||||
activeAssistant: Assistant
|
||||
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 activeTopic = useAppSelector((state) => state.runtime.chat.activeTopic) || topics[0]
|
||||
const [activeTopic, setActiveTopic] = useState<Topic>(topics[0])
|
||||
const { clickAssistantToShowTopic } = useSettings()
|
||||
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(() => {
|
||||
if (activeTopic) {
|
||||
dispatch(loadTopicMessagesThunk(activeTopic.id))
|
||||
@ -24,28 +45,38 @@ export const useChat = () => {
|
||||
}
|
||||
}, [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(() => {
|
||||
if (clickAssistantToShowTopic) {
|
||||
EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)
|
||||
}
|
||||
}, [clickAssistantToShowTopic, activeAssistant])
|
||||
|
||||
return {
|
||||
activeAssistant,
|
||||
activeTopic,
|
||||
setActiveAssistant: (assistant: Assistant) => {
|
||||
dispatch(setActiveAssistant(assistant))
|
||||
},
|
||||
setActiveTopic: (topic: Topic) => {
|
||||
dispatch(setActiveTopic(topic))
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
const subscriptions = [
|
||||
EventEmitter.on(EVENT_NAMES.SET_ASSISTANT, setActiveAssistant),
|
||||
EventEmitter.on(EVENT_NAMES.SET_TOPIC, setActiveTopic)
|
||||
]
|
||||
return () => subscriptions.forEach((subscription) => subscription())
|
||||
}, [])
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ 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 { setSelectedMessageIds, toggleMultiSelectMode } from '@renderer/store/runtime'
|
||||
import { Topic } from '@renderer/types'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -27,10 +27,6 @@ export const useChatContext = (activeTopic: Topic) => {
|
||||
return () => unsubscribe()
|
||||
}, [dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setActiveTopic(activeTopic))
|
||||
}, [dispatch, activeTopic])
|
||||
|
||||
const handleToggleMultiSelectMode = useCallback(
|
||||
(value: boolean) => {
|
||||
dispatch(toggleMultiSelectMode(value))
|
||||
|
||||
@ -29,7 +29,9 @@ const App: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isHome) {
|
||||
navigate('/')
|
||||
setTimeout(() => {
|
||||
navigate('/')
|
||||
}, 300)
|
||||
}
|
||||
|
||||
openMinappKeepAlive(app)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { ChatProvider } from '@renderer/hooks/useChat'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
|
||||
import { Topic } from '@renderer/types'
|
||||
@ -72,51 +73,53 @@ const TopicsPage: FC = () => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<HStack style={{ padding: '0 12px', marginTop: 8 }}>
|
||||
<Input
|
||||
prefix={
|
||||
stack.length > 1 ? (
|
||||
<SearchIcon className="back-icon" onClick={goBack}>
|
||||
<ChevronLeft size={16} />
|
||||
</SearchIcon>
|
||||
) : (
|
||||
<SearchIcon>
|
||||
<Search size={15} />
|
||||
</SearchIcon>
|
||||
)
|
||||
}
|
||||
suffix={search.length >= 2 ? <CornerDownLeft size={16} /> : null}
|
||||
ref={inputRef}
|
||||
placeholder={t('history.search.placeholder')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value.trimStart())}
|
||||
allowClear
|
||||
autoFocus
|
||||
spellCheck={false}
|
||||
style={{ paddingLeft: 0 }}
|
||||
variant="borderless"
|
||||
size="middle"
|
||||
onPressEnter={onSearch}
|
||||
/>
|
||||
</HStack>
|
||||
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
|
||||
<ChatProvider>
|
||||
<Container>
|
||||
<HStack style={{ padding: '0 12px', marginTop: 8 }}>
|
||||
<Input
|
||||
prefix={
|
||||
stack.length > 1 ? (
|
||||
<SearchIcon className="back-icon" onClick={goBack}>
|
||||
<ChevronLeft size={16} />
|
||||
</SearchIcon>
|
||||
) : (
|
||||
<SearchIcon>
|
||||
<Search size={15} />
|
||||
</SearchIcon>
|
||||
)
|
||||
}
|
||||
suffix={search.length >= 2 ? <CornerDownLeft size={16} /> : null}
|
||||
ref={inputRef}
|
||||
placeholder={t('history.search.placeholder')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value.trimStart())}
|
||||
allowClear
|
||||
autoFocus
|
||||
spellCheck={false}
|
||||
style={{ paddingLeft: 0 }}
|
||||
variant="borderless"
|
||||
size="middle"
|
||||
onPressEnter={onSearch}
|
||||
/>
|
||||
</HStack>
|
||||
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
|
||||
|
||||
<TopicsHistory
|
||||
keywords={search}
|
||||
onClick={onTopicClick as any}
|
||||
onSearch={onSearch}
|
||||
style={{ display: isShow('topics') }}
|
||||
/>
|
||||
<TopicMessages topic={topic} style={{ display: isShow('topic') }} />
|
||||
<SearchResults
|
||||
keywords={isShow('search') ? searchKeywords : ''}
|
||||
onMessageClick={onMessageClick}
|
||||
onTopicClick={onTopicClick}
|
||||
style={{ display: isShow('search') }}
|
||||
/>
|
||||
<SearchMessage message={message} style={{ display: isShow('message') }} />
|
||||
</Container>
|
||||
<TopicsHistory
|
||||
keywords={search}
|
||||
onClick={onTopicClick as any}
|
||||
onSearch={onSearch}
|
||||
style={{ display: isShow('topics') }}
|
||||
/>
|
||||
<TopicMessages topic={topic} style={{ display: isShow('topic') }} />
|
||||
<SearchResults
|
||||
keywords={isShow('search') ? searchKeywords : ''}
|
||||
onMessageClick={onMessageClick}
|
||||
onTopicClick={onTopicClick}
|
||||
style={{ display: isShow('search') }}
|
||||
/>
|
||||
<SearchMessage message={message} style={{ display: isShow('message') }} />
|
||||
</Container>
|
||||
</ChatProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ArrowRightOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
|
||||
import { useChat } from '@renderer/hooks/useChat'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
|
||||
@ -22,7 +21,6 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
const { messageStyle } = useSettings()
|
||||
const { t } = useTranslation()
|
||||
const [topic, setTopic] = useState<Topic | null>(null)
|
||||
const { setActiveAssistant, setActiveTopic } = useChat()
|
||||
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
@ -50,13 +48,11 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
type="text"
|
||||
size="middle"
|
||||
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 10 }}
|
||||
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })}
|
||||
onClick={() => locateToMessage(message)}
|
||||
icon={<ArrowRightOutlined />}
|
||||
/>
|
||||
<HStack mt="10px" justifyContent="center">
|
||||
<Button
|
||||
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })}
|
||||
icon={<ArrowRightOutlined />}>
|
||||
<Button onClick={() => locateToMessage(message)} icon={<ArrowRightOutlined />}>
|
||||
{t('history.locate.message')}
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
@ -59,7 +59,7 @@ const TopicMessages: FC<Props> = ({ topic, ...props }) => {
|
||||
type="text"
|
||||
size="middle"
|
||||
style={{ color: 'var(--color-text-3)', position: 'absolute', right: 0, top: 5 }}
|
||||
onClick={() => locateToMessage({ message, setActiveAssistant, setActiveTopic })}
|
||||
onClick={() => locateToMessage(message)}
|
||||
icon={<ArrowRightOutlined />}
|
||||
/>
|
||||
<Divider style={{ margin: '8px auto 15px' }} variant="dashed" />
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { ChatProvider } from '@renderer/hooks/useChat'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { FC, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
@ -19,15 +20,17 @@ const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => {
|
||||
}, [showAssistants, showTopics, topicPosition])
|
||||
|
||||
return (
|
||||
<HStack style={{ display: 'flex', flex: 1 }} id="home-page">
|
||||
<MainSidebar />
|
||||
<Container style={style}>
|
||||
<ChatNavbar />
|
||||
<ContentContainer id="content-container">
|
||||
<Chat />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
</HStack>
|
||||
<ChatProvider>
|
||||
<HStack style={{ display: 'flex', flex: 1 }} id="home-page">
|
||||
<MainSidebar />
|
||||
<Container style={style}>
|
||||
<ChatNavbar />
|
||||
<ContentContainer id="content-container">
|
||||
<Chat />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
</HStack>
|
||||
</ChatProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ import {
|
||||
SubMenu
|
||||
} from './MainSidebarStyles'
|
||||
import OpenedMinappTabs from './OpenedMinapps'
|
||||
import PinnedApps from './PinnedApps'
|
||||
|
||||
type Tab = 'assistants' | 'topic'
|
||||
|
||||
@ -174,7 +173,7 @@ const MainSidebar: FC = () => {
|
||||
overflow: showAssistants ? 'initial' : 'hidden'
|
||||
}}>
|
||||
<MainNavbar />
|
||||
<MainMenu>
|
||||
<MainMenu style={{ marginBottom: 4 }}>
|
||||
<MainMenuItem active={isAppMenuExpanded} onClick={() => setIsAppMenuExpanded(!isAppMenuExpanded)}>
|
||||
<MainMenuItemLeft>
|
||||
<MainMenuItemIcon>
|
||||
@ -200,7 +199,6 @@ const MainSidebar: FC = () => {
|
||||
</MainMenuItemLeft>
|
||||
</MainMenuItem>
|
||||
))}
|
||||
<PinnedApps />
|
||||
</SubMenu>
|
||||
)}
|
||||
<OpenedMinappTabs />
|
||||
@ -299,9 +297,7 @@ const MainContainer = styled.div`
|
||||
`
|
||||
|
||||
const AssistantContainer = styled.div`
|
||||
margin: 0 10px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
margin: 4px 10px;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
|
||||
@ -85,7 +85,6 @@ export const TabsContainer = styled.div`
|
||||
|
||||
export const TabsWrapper = styled(Scrollbar as any)`
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
max-height: 50vh;
|
||||
`
|
||||
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import MinAppIcon from '@renderer/components/Icons/MinAppIcon'
|
||||
import IndicatorLight from '@renderer/components/IndicatorLight'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { Empty } from 'antd'
|
||||
import { Dropdown } from 'antd'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { FC, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -18,7 +20,6 @@ import {
|
||||
MainMenuItemLeft,
|
||||
MainMenuItemRight,
|
||||
MainMenuItemText,
|
||||
Menus,
|
||||
TabsContainer,
|
||||
TabsWrapper
|
||||
} from './MainSidebarStyles'
|
||||
@ -27,8 +28,24 @@ const OpenedMinapps: FC = () => {
|
||||
const { minappShow, openedKeepAliveMinapps, currentMinappId } = useRuntime()
|
||||
const { openMinappKeepAlive, hideMinappPopup, closeMinapp, closeAllMinapps } = useMinappPopup()
|
||||
const { showOpenedMinappsInSidebar } = useSettings()
|
||||
const { pinned, updatePinnedMinapps } = useMinapps()
|
||||
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) => {
|
||||
if (minappShow && currentMinappId === app.id) {
|
||||
hideMinappPopup()
|
||||
@ -59,51 +76,80 @@ const OpenedMinapps: FC = () => {
|
||||
container.style.setProperty('--indicator-right', `${indicatorRight}px`)
|
||||
}, [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 (
|
||||
<TabsContainer className="TabsContainer">
|
||||
<Divider />
|
||||
<TabsWrapper>
|
||||
<Menus>
|
||||
{openedKeepAliveMinapps.map((app) => {
|
||||
<DragableList
|
||||
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'] = [
|
||||
{
|
||||
key: 'closeApp',
|
||||
label: t('minapp.sidebar.close.title'),
|
||||
onClick: () => closeMinapp(app.id)
|
||||
},
|
||||
{
|
||||
key: 'closeAllApp',
|
||||
label: t('minapp.sidebar.closeall.title'),
|
||||
onClick: () => closeAllMinapps()
|
||||
key: 'togglePin',
|
||||
label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.pin.title'),
|
||||
onClick: () => {
|
||||
if (isPinned) {
|
||||
const newPinned = pinned.filter((item) => item.id !== app.id)
|
||||
updatePinnedMinapps(newPinned)
|
||||
} else {
|
||||
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 (
|
||||
<MainMenuItem key={app.id} onClick={() => handleOnClick(app)}>
|
||||
<MainMenuItemLeft>
|
||||
<MainMenuItemIcon>
|
||||
<MinAppIcon size={22} app={app} style={{ borderRadius: 6 }} sidebar />
|
||||
</MainMenuItemIcon>
|
||||
<MainMenuItemText>{app.name}</MainMenuItemText>
|
||||
</MainMenuItemLeft>
|
||||
<MainMenuItemRight style={{ marginRight: 4 }}>
|
||||
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}>
|
||||
<IndicatorLight color="var(--color-primary)" shadow={false} animation={false} size={5} />
|
||||
</Dropdown>
|
||||
</MainMenuItemRight>
|
||||
</MainMenuItem>
|
||||
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}>
|
||||
<MainMenuItem key={app.id} onClick={() => handleOnClick(app)}>
|
||||
<MainMenuItemLeft>
|
||||
<MainMenuItemIcon>
|
||||
<MinAppIcon size={22} app={app} style={{ borderRadius: 6 }} sidebar />
|
||||
</MainMenuItemIcon>
|
||||
<MainMenuItemText>{app.name}</MainMenuItemText>
|
||||
</MainMenuItemLeft>
|
||||
{isOpened && (
|
||||
<MainMenuItemRight style={{ marginRight: 4 }}>
|
||||
<IndicatorLight color="var(--color-primary)" shadow={false} animation={false} size={5} />
|
||||
</MainMenuItemRight>
|
||||
)}
|
||||
</MainMenuItem>
|
||||
</Dropdown>
|
||||
)
|
||||
})}
|
||||
{isEmpty(openedKeepAliveMinapps) && (
|
||||
<Center>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</Center>
|
||||
)}
|
||||
</Menus>
|
||||
}}
|
||||
</DragableList>
|
||||
{isEmpty(sortedApps) && (
|
||||
<Center>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</Center>
|
||||
)}
|
||||
</TabsWrapper>
|
||||
<Divider />
|
||||
</TabsContainer>
|
||||
|
||||
@ -29,5 +29,7 @@ export const EVENT_NAMES = {
|
||||
SHOW_MODEL_SELECTOR: 'SHOW_MODEL_SELECTOR',
|
||||
EDIT_CODE_BLOCK: 'EDIT_CODE_BLOCK',
|
||||
CHANGE_TOPIC: 'CHANGE_TOPIC',
|
||||
OPEN_MINAPP: 'OPEN_MINAPP'
|
||||
OPEN_MINAPP: 'OPEN_MINAPP',
|
||||
SET_ASSISTANT: 'SET_ASSISTANT',
|
||||
SET_TOPIC: 'SET_TOPIC'
|
||||
}
|
||||
|
||||
@ -81,15 +81,7 @@ export function isGenerating() {
|
||||
})
|
||||
}
|
||||
|
||||
export async function locateToMessage({
|
||||
message,
|
||||
setActiveAssistant,
|
||||
setActiveTopic
|
||||
}: {
|
||||
message: Message
|
||||
setActiveAssistant: (assistant: Assistant) => void
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
}) {
|
||||
export async function locateToMessage(message: Message) {
|
||||
await isGenerating()
|
||||
|
||||
SearchPopup.hide()
|
||||
@ -100,11 +92,11 @@ export async function locateToMessage({
|
||||
return
|
||||
}
|
||||
|
||||
setActiveAssistant(assistant)
|
||||
setActiveTopic(topic)
|
||||
EventEmitter.emit(EVENT_NAMES.SET_ASSISTANT, assistant)
|
||||
EventEmitter.emit(EVENT_NAMES.SET_TOPIC, topic)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
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'
|
||||
|
||||
export interface ChatState {
|
||||
isMultiSelectMode: boolean
|
||||
selectedMessageIds: string[]
|
||||
activeTopic: Topic | null
|
||||
activeAssistant: Assistant | null
|
||||
/** topic ids that are currently being renamed */
|
||||
renamingTopics: string[]
|
||||
/** topic ids that are newly renamed */
|
||||
@ -70,8 +68,6 @@ const initialState: RuntimeState = {
|
||||
chat: {
|
||||
isMultiSelectMode: false,
|
||||
selectedMessageIds: [],
|
||||
activeTopic: null,
|
||||
activeAssistant: null,
|
||||
renamingTopics: [],
|
||||
newlyRenamedTopics: []
|
||||
}
|
||||
@ -124,12 +120,6 @@ const runtimeSlice = createSlice({
|
||||
setSelectedMessageIds: (state, action: PayloadAction<string[]>) => {
|
||||
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[]>) => {
|
||||
state.chat.renamingTopics = action.payload
|
||||
},
|
||||
@ -154,8 +144,6 @@ export const {
|
||||
// Chat related actions
|
||||
toggleMultiSelectMode,
|
||||
setSelectedMessageIds,
|
||||
setActiveTopic,
|
||||
setActiveAssistant,
|
||||
setRenamingTopics,
|
||||
setNewlyRenamedTopics
|
||||
} = runtimeSlice.actions
|
||||
|
||||
Loading…
Reference in New Issue
Block a user