Skip to content
This repository has been archived by the owner on Feb 19, 2025. It is now read-only.

Commit

Permalink
useChatContext, design
Browse files Browse the repository at this point in the history
  • Loading branch information
Niklas Kerkhoff committed Nov 22, 2024
1 parent 136f469 commit 0934a99
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 97 deletions.
5 changes: 4 additions & 1 deletion tutor-assistent-web/src/app/routing/Routing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChatPage } from '../../modules/chat/ChatPage.tsx'
import { Authenticated } from '../auth/Authenticated.tsx'
import { DocumentsPage } from '../../modules/documents/DocumentsPage.tsx'
import { HelperPage } from '../../modules/helper/HelperPage.tsx'
import { ChatProvider } from '../../modules/chat/ChatProvider.tsx'

interface Props {

Expand All @@ -18,7 +19,9 @@ export function Routing({}: Props) {
<Route
path='/chats/:chatId?' element={
<Authenticated>
<ChatPage />
<ChatProvider>
<ChatPage />
</ChatProvider>
</Authenticated>
}
/>
Expand Down
2 changes: 1 addition & 1 deletion tutor-assistent-web/src/common/components/Bar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { styled } from '@mui/joy'
import { VStack } from '../../lib/components/flex-layout.tsx'

const barWidth = '420px'
const barWidth = '380px'
export const Bar = styled(VStack)`
min-width: ${barWidth};
width: ${barWidth};
Expand Down
9 changes: 7 additions & 2 deletions tutor-assistent-web/src/common/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { ReactNode } from 'react'
import { isPresent } from '../../lib/utils/utils.ts'

interface Props {
title: string
title: ReactNode | string
leftNode?: ReactNode
rightNode?: ReactNode
}
Expand All @@ -14,7 +14,12 @@ export function Header({ title, leftNode, rightNode }: Props) {
<>
<Row alignItems='center' height={36}>
<Spacer>{isPresent(leftNode) && leftNode}</Spacer>
<Typography level='title-lg'>{title}</Typography>
{
typeof title === 'string'
? <Typography level='title-lg'>{title}</Typography>
: title
}

<Spacer sx={{ textAlign: 'end' }}>{isPresent(rightNode) && rightNode}</Spacer>
</Row>
<Divider />
Expand Down
11 changes: 6 additions & 5 deletions tutor-assistent-web/src/modules/chat/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { isNotPresent, isPresent } from '../../lib/utils/utils.ts'
import { useChatManager } from './hooks/useChatManager.ts'
import { useTranslation } from 'react-i18next'
import { useChat } from './hooks/useChat.ts'
import { useSelectedChat } from './hooks/useSelectedChat.ts'
import { useAsyncActionTrigger } from './hooks/useAsyncActionTrigger.ts'
import { MainContent, Row, VStack } from '../../lib/components/flex-layout.tsx'
import { Divider } from '@mui/joy'
Expand All @@ -20,11 +20,12 @@ export function ChatPage() {

const chatId = useParams().chatId
const { createChat } = useChatManager()
const { chat, sendMessage, isLoading } = useChat(chatId)
const { selectedChat, sendMessage, isLoading } = useSelectedChat(chatId)
console.log('selectedChat', selectedChat)
const [isSending, sendMessageAction] = useAsyncActionTrigger(
handleSend,
() => isPresent(chat) && chat.id === chatId,
[chat?.id],
() => isPresent(selectedChat) && selectedChat.id === chatId,
[selectedChat?.id],
)


Expand Down Expand Up @@ -63,7 +64,7 @@ export function ChatPage() {
<MainContent>
{
isPresent(chatId)
? <ChatDetails chat={chat} />
? <ChatDetails />
: <ChatOverview />
}
</MainContent>
Expand Down
41 changes: 35 additions & 6 deletions tutor-assistent-web/src/modules/chat/ChatProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import { createContext, useState } from 'react'
import { Chat } from './chat-model.ts'
import { createContext, Dispatch, SetStateAction, useState } from 'react'
import { Chat, ChatMessageFeedback } from './chat-model.ts'
import { ChildrenProps } from '../../lib/types.ts'
import { chill } from '../../lib/utils/utils.ts'
import { ChildrenProps, State } from '../../lib/types.ts'

type ChatContextType = {
chats: Chat[]
setChats: Dispatch<SetStateAction<Chat[]>>
selectedChat: Chat | undefined
setSelectedChat: Dispatch<SetStateAction<Chat | undefined>>
selectedMessageId: string | undefined
setSelectedMessageId: Dispatch<SetStateAction<string | undefined>>
selectedMessageFeedback: ChatMessageFeedback | undefined
setSelectedMessageFeedback: Dispatch<SetStateAction<ChatMessageFeedback | undefined>>
}

export const ChatContext = createContext<State<Chat>>([undefined, chill])
export const ChatContext = createContext<ChatContextType>({
chats: [],
setChats: chill,
selectedChat: undefined,
setSelectedChat: chill,
selectedMessageId: undefined,
setSelectedMessageId: chill,
selectedMessageFeedback: undefined,
setSelectedMessageFeedback: chill,
})

export function ChatProvider({ children }: ChildrenProps) {
const state = useState<Chat>()
const [chats, setChats] = useState<Chat[]>([])
const [selectedChat, setSelectedChat] = useState<Chat>()
const [selectedMessageId, setSelectedMessageId] = useState<string>()
const [selectedMessageFeedback, setSelectedMessageFeedback] = useState<ChatMessageFeedback>()

const value: ChatContextType = {
chats, setChats,
selectedChat, setSelectedChat,
selectedMessageId, setSelectedMessageId,
selectedMessageFeedback, setSelectedMessageFeedback,
}

return (
<ChatContext.Provider value={state}>
<ChatContext.Provider value={value}>
{children}
</ChatContext.Provider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { Header } from '../../../../common/components/Header.tsx'
import { useTranslation } from 'react-i18next'
import { Scroller } from '../../../../lib/components/Scroller.tsx'
import { MainContent, Spacer, VStack } from '../../../../lib/components/flex-layout.tsx'
import { Box, Button, Card, CardActions, CardContent, IconButton, Typography } from '@mui/joy'
import { GradingOutlined, SummarizeOutlined } from '@mui/icons-material'
import { Box, Button, Card, CardActions, CardContent, ToggleButtonGroup, Typography } from '@mui/joy'
import { empty } from '../../../../lib/utils/array-utils.ts'
import { StyledMarkdown } from '../../../../common/components/StyledMarkdown.tsx'
import { useOpenContexts } from '../../hooks/useOpenContexts.ts'
Expand All @@ -20,55 +19,56 @@ interface Props {
}

export function ChatAdditionalInfo({ chat, selectedMessageId }: Props) {
const [additionalInfo, setAdditionalInfo] = useState<'summary' | 'contexts' | undefined>('summary')
const { t } = useTranslation()
const [additionalInfo, setAdditionalInfo] = useState<'summary' | 'contexts' | null>('contexts')
const contexts = useMemo(() => {
if (isNotPresent(selectedMessageId) && empty(chat.messages)) return undefined
return chat.messages.find(message => message.id === selectedMessageId)?.contexts
}, [selectedMessageId])

function handleTabChange(_: unknown, newValue: 'summary' | 'contexts' | null) {
if (isPresent(newValue)) {
setAdditionalInfo(newValue)
}
}

if (isNotPresent(additionalInfo)) return <></>

return (
<Bar className='right'>
{
additionalInfo === 'summary' && (
<Summary
summary={chat.summary}
onContextsClicked={() => setAdditionalInfo('contexts')}
/>
)
return <Bar className='right'>
<Header
title={
<ToggleButtonGroup
value={additionalInfo}
onChange={handleTabChange}
size='sm'
>
<Button value='contexts'>{t('Sources')} ({contexts?.length ?? 0})</Button>
<Button value='summary'>{t('Summary')}</Button>
</ToggleButtonGroup>
}
/>
{
additionalInfo === 'summary' && <Summary
summary={chat.summary}
/>
}

{
additionalInfo === 'contexts' && (
<Contexts
contexts={contexts}
onSummaryClicked={() => setAdditionalInfo('summary')}
/>
)
}
</Bar>
)
{
additionalInfo === 'contexts' && <Contexts
contexts={contexts}
/>
}
</Bar>
}

interface SummaryProps {
summary: ChatSummary | undefined
onContextsClicked: () => void
}

function Summary({ summary, onContextsClicked }: SummaryProps) {
const { t } = useTranslation()
function Summary({ summary }: SummaryProps) {

return (
<>
<Header
title={t('Summary')}
rightNode={
<IconButton color='primary' onClick={onContextsClicked}>
<GradingOutlined />
</IconButton>
}
/>
<MainContent>
<Scroller padding={1}>
{
Expand All @@ -89,10 +89,9 @@ function Summary({ summary, onContextsClicked }: SummaryProps) {

interface ContextsProps {
contexts: ChatMessageContext[] | undefined
onSummaryClicked: () => void
}

function Contexts({ contexts, onSummaryClicked }: ContextsProps) {
function Contexts({ contexts }: ContextsProps) {
const { t } = useTranslation()

const { openContexts } = useOpenContexts()
Expand All @@ -104,16 +103,14 @@ function Contexts({ contexts, onSummaryClicked }: ContextsProps) {
return isPresent(context.title) ? `${context.title}${pageOutput}` : ''
}

if (contexts.length === 0) return (
<VStack justifyContent='center' alignItems='center'>
<Typography>{t('Select a message')}</Typography>
</VStack>
)

return (
<>
<Header
title={`${t('Sources')} (${contexts?.length ?? 0})`}
rightNode={
<IconButton color='primary' onClick={onSummaryClicked}>
<SummarizeOutlined />
</IconButton>
}
/>
<MainContent>
<Scroller padding={1}>
<VStack gap={1}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import { HStack, MainContent, VStack } from '../../../../lib/components/flex-layout.tsx'
import { ChatMessageList } from './ChatMessageList.tsx'
import React, { useEffect, useState } from 'react'
import React, { useEffect } from 'react'
import { Scroller } from '../../../../lib/components/Scroller.tsx'
import { Header } from '../../../../common/components/Header.tsx'
import { useTranslation } from 'react-i18next'
import { IconButton } from '@mui/joy'
import { useNavigate } from 'react-router-dom'
import { ArrowBackIosNew } from '@mui/icons-material'
import { isNotPresent } from '../../../../lib/utils/utils.ts'
import { Chat } from '../../chat-model.ts'
import { last } from '../../../../lib/utils/array-utils.ts'
import { ChatAdditionalInfo } from './ChatAdditionalInfo.tsx'
import { useChatContext } from '../../useChatContext.ts'


interface Props {
chat: Chat | undefined
}

export function ChatDetails({ chat }: Props) {
export function ChatDetails() {
const { t } = useTranslation()
const navigate = useNavigate()

const [selectedMessageId, setSelectedMessageId] = useState<string>()

const { selectedChat, selectedMessageId, setSelectedMessageId } = useChatContext()

useEffect(() => {
if (isNotPresent(chat)) return
setSelectedMessageId(last(chat.messages)?.id)
}, [chat?.messages.length])
console.log('hier', selectedChat)
if (isNotPresent(selectedChat)) return
setSelectedMessageId(last(selectedChat.messages)?.id)
}, [selectedChat?.messages.length])

if (isNotPresent(chat)) return <></>
if (isNotPresent(selectedChat)) return <></>

return (
<HStack>
Expand All @@ -40,27 +41,27 @@ export function ChatDetails({ chat }: Props) {
<ArrowBackIosNew />
</IconButton>
}
title={chat.summary?.title ?? t('New Chat')}
title={selectedChat.summary?.title ?? t('New Chat')}
/>

<MainContent>
<Scroller
padding={1}
scrollToBottomOnChange={[
chat.messages.length,
last(chat.messages)?.content?.length ?? 0,
selectedChat,
last(selectedChat.messages)?.content?.length ?? 0
]}
>
<ChatMessageList
messages={chat.messages}
messages={selectedChat.messages}
onMessageClick={id => setSelectedMessageId(id)}
selectedMessageId={selectedMessageId}
/>
</Scroller>
</MainContent>
</VStack>
</MainContent>
<ChatAdditionalInfo chat={chat} selectedMessageId={selectedMessageId} />
<ChatAdditionalInfo chat={selectedChat} selectedMessageId={selectedMessageId} />
</HStack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ interface Props {
export function ChatMessageItem({ message, onMessageClick, selectedMessageId }: Props) {
const { t } = useTranslation()

const { feedback, setContent, loadFeedback, updateFeedbackRating, updateFeedbackContent } = useChatMessageFeedback()
const {
selectedMessageFeedback,
setContent,
loadFeedback,
updateFeedbackRating,
updateFeedbackContent,
} = useChatMessageFeedback()
const [showThankYou, setShowThankYou] = useState(false)

useEffect(() => {
Expand All @@ -36,7 +42,7 @@ export function ChatMessageItem({ message, onMessageClick, selectedMessageId }:
async function handleSendFeedback() {
if (isNotPresent(selectedMessageId)) return

await updateFeedbackContent(selectedMessageId, feedback?.content ?? '')
await updateFeedbackContent(selectedMessageId, selectedMessageFeedback?.content ?? '')
setShowThankYou(true)
}

Expand Down Expand Up @@ -67,7 +73,7 @@ export function ChatMessageItem({ message, onMessageClick, selectedMessageId }:
{isSelected(message.id) && (
<SubmitTextarea
onCtrlEnter={handleSendFeedback}
value={feedback?.content ?? ''}
value={selectedMessageFeedback?.content ?? ''}
placeholder={t('Feedback')}
onChange={e => {
setContent(e.target.value)
Expand All @@ -76,7 +82,7 @@ export function ChatMessageItem({ message, onMessageClick, selectedMessageId }:
startDecorator={
<StarRater
max={5}
rating={feedback?.rating ?? 0}
rating={selectedMessageFeedback?.rating ?? 0}
onSelect={rating => updateFeedbackRating(selectedMessageId, rating)}
/>
}
Expand Down
Loading

0 comments on commit 0934a99

Please sign in to comment.