feat(chat): add session messages view and active state tracking

Implement agent session messages display component and track active topic/session state
Add AgentSessionMessages component and integrate with chat view
Update topic and session selection to set active state in store
This commit is contained in:
icarus 2025-09-19 16:39:02 +08:00
parent 64e3de9ada
commit 5ddf9683b4
4 changed files with 99 additions and 18 deletions

View File

@ -6,6 +6,7 @@ import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { QuickPanelProvider } from '@renderer/components/QuickPanel' import { QuickPanelProvider } from '@renderer/components/QuickPanel'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useChatContext } from '@renderer/hooks/useChatContext' import { useChatContext } from '@renderer/hooks/useChatContext'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings' import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts' import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
@ -23,6 +24,7 @@ import styled from 'styled-components'
import ChatNavbar from './ChatNavbar' import ChatNavbar from './ChatNavbar'
import Inputbar from './Inputbar/Inputbar' import Inputbar from './Inputbar/Inputbar'
import AgentSessionMessages from './Messages/AgentSessionMessages'
import ChatNavigation from './Messages/ChatNavigation' import ChatNavigation from './Messages/ChatNavigation'
import Messages from './Messages/Messages' import Messages from './Messages/Messages'
import Tabs from './Tabs' import Tabs from './Tabs'
@ -44,6 +46,8 @@ const Chat: FC<Props> = (props) => {
const { isMultiSelectMode } = useChatContext(props.activeTopic) const { isMultiSelectMode } = useChatContext(props.activeTopic)
const { isTopNavbar } = useNavbarPosition() const { isTopNavbar } = useNavbarPosition()
const chatMaxWidth = useChatMaxWidth() const chatMaxWidth = useChatMaxWidth()
const { chat } = useRuntime()
const { activeTopicOrSession, activeAgentId, activeSessionId } = chat
const mainRef = React.useRef<HTMLDivElement>(null) const mainRef = React.useRef<HTMLDivElement>(null)
const contentSearchRef = React.useRef<ContentSearchRef>(null) const contentSearchRef = React.useRef<ContentSearchRef>(null)
@ -136,6 +140,17 @@ const Chat: FC<Props> = (props) => {
? 'calc(100vh - var(--navbar-height) - var(--navbar-height) - 12px)' ? 'calc(100vh - var(--navbar-height) - var(--navbar-height) - 12px)'
: 'calc(100vh - var(--navbar-height))' : 'calc(100vh - var(--navbar-height))'
const SessionMessages = () => {
if (activeAgentId === null) {
return <div> Active Agent ID is invalid.</div>
}
const sessionId = activeSessionId[activeAgentId]
if (!sessionId) {
return <div> Active Session ID is invalid.</div>
}
return <AgentSessionMessages agentId={activeAgentId} sessionId={sessionId} />
}
return ( return (
<Container id="chat" className={classNames([messageStyle, { 'multi-select-mode': isMultiSelectMode }])}> <Container id="chat" className={classNames([messageStyle, { 'multi-select-mode': isMultiSelectMode }])}>
{isTopNavbar && ( {isTopNavbar && (
@ -156,23 +171,28 @@ const Chat: FC<Props> = (props) => {
justify="space-between" justify="space-between"
style={{ maxWidth: chatMaxWidth, height: mainHeight }}> style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
<QuickPanelProvider> <QuickPanelProvider>
<Messages {activeTopicOrSession === 'topic' && (
key={props.activeTopic.id} <>
assistant={assistant} <Messages
topic={props.activeTopic} key={props.activeTopic.id}
setActiveTopic={props.setActiveTopic} assistant={assistant}
onComponentUpdate={messagesComponentUpdateHandler} topic={props.activeTopic}
onFirstUpdate={messagesComponentFirstUpdateHandler} setActiveTopic={props.setActiveTopic}
/> onComponentUpdate={messagesComponentUpdateHandler}
<ContentSearch onFirstUpdate={messagesComponentFirstUpdateHandler}
ref={contentSearchRef} />
searchTarget={mainRef as React.RefObject<HTMLElement>} <ContentSearch
filter={contentSearchFilter} ref={contentSearchRef}
includeUser={filterIncludeUser} searchTarget={mainRef as React.RefObject<HTMLElement>}
onIncludeUserChange={userOutlinedItemClickHandler} filter={contentSearchFilter}
/> includeUser={filterIncludeUser}
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />} onIncludeUserChange={userOutlinedItemClickHandler}
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} /> />
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
</>
)}
{activeTopicOrSession === 'session' && <SessionMessages />}
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />} {isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
</QuickPanelProvider> </QuickPanelProvider>
</Main> </Main>

View File

@ -5,6 +5,7 @@ import { useActiveTopic } from '@renderer/hooks/useTopic'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import NavigationService from '@renderer/services/NavigationService' import NavigationService from '@renderer/services/NavigationService'
import { newMessagesActions } from '@renderer/store/newMessage' import { newMessagesActions } from '@renderer/store/newMessage'
import { setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SECOND_MIN_WINDOW_WIDTH } from '@shared/config/constant' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SECOND_MIN_WINDOW_WIDTH } from '@shared/config/constant'
import { AnimatePresence, motion } from 'motion/react' import { AnimatePresence, motion } from 'motion/react'
@ -52,6 +53,7 @@ const HomePage: FC = () => {
startTransition(() => { startTransition(() => {
_setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic)) _setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic))
dispatch(newMessagesActions.setTopicFulfilled({ topicId: newTopic.id, fulfilled: false })) dispatch(newMessagesActions.setTopicFulfilled({ topicId: newTopic.id, fulfilled: false }))
dispatch(setActiveTopicOrSessionAction('topic'))
}) })
}, },
[_setActiveTopic, dispatch] [_setActiveTopic, dispatch]

View File

@ -0,0 +1,58 @@
import ContextMenu from '@renderer/components/ContextMenu'
import Scrollbar from '@renderer/components/Scrollbar'
import { useSession } from '@renderer/hooks/agents/useSession'
import { memo } from 'react'
import styled from 'styled-components'
import NarrowLayout from './NarrowLayout'
type Props = {
agentId: string
sessionId: string
}
const AgentSessionMessages: React.FC<Props> = ({ agentId, sessionId }) => {
const { messages } = useSession(agentId, sessionId)
return (
<MessagesContainer id="messages" className="messages-container">
<NarrowLayout style={{ display: 'flex', flexDirection: 'column-reverse' }}>
<ContextMenu>
<ScrollContainer>
{messages.map((message) => {
const content = message.content.content
if (typeof content === 'string') {
return <div key={message.id}>{content}</div>
} else {
return 'Not string content'
}
})}
</ScrollContainer>
</ContextMenu>
</NarrowLayout>
</MessagesContainer>
)
}
const ScrollContainer = styled.div`
display: flex;
flex-direction: column-reverse;
padding: 10px 10px 20px;
.multi-select-mode & {
padding-bottom: 60px;
}
`
interface ContainerProps {
$right?: boolean
}
const MessagesContainer = styled(Scrollbar)<ContainerProps>`
display: flex;
flex-direction: column-reverse;
overflow-x: hidden;
z-index: 1;
position: relative;
`
export default memo(AgentSessionMessages)

View File

@ -2,7 +2,7 @@ import { Button, Spinner } from '@heroui/react'
import { SessionModal } from '@renderer/components/Popups/agent/SessionModal' import { SessionModal } from '@renderer/components/Popups/agent/SessionModal'
import { useSessions } from '@renderer/hooks/agents/useSessions' import { useSessions } from '@renderer/hooks/agents/useSessions'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setActiveSessionIdAction } from '@renderer/store/runtime' import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -23,6 +23,7 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
const setActiveSessionId = useCallback( const setActiveSessionId = useCallback(
(agentId: string, sessionId: string | null) => { (agentId: string, sessionId: string | null) => {
dispatch(setActiveSessionIdAction({ agentId, sessionId })) dispatch(setActiveSessionIdAction({ agentId, sessionId }))
dispatch(setActiveTopicOrSessionAction('session'))
}, },
[dispatch] [dispatch]
) )