Restructure chat interface to display history side-by-side with live chat

- Change chat layout from vertical stacking to horizontal side-by-side layout
- History panel (300px width) displays on left with conversation list
- Main chat area takes remaining space on right with messages and input
- Improves UX by making chat history visible alongside active conversation
- Fix JSX structure and nesting for proper component rendering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
andupetcu
2025-09-22 12:57:09 +03:00
parent 24c0577f44
commit c82b3007fd

View File

@@ -536,38 +536,48 @@ export default function FloatingChat() {
{!isMinimized && ( {!isMinimized && (
<> <>
{/* Chat History Panel */} {/* Main Content Area - Side by Side Layout */}
{showHistory && ( <Box sx={{
<Box sx={{ display: 'flex',
p: 2, flexGrow: 1,
borderBottom: 1, overflow: 'hidden'
borderColor: 'divider', }}>
bgcolor: 'grey.50', {/* Chat History Panel - Left Side */}
maxHeight: '300px', {showHistory && (
overflowY: 'auto' <Box sx={{
}}> width: '300px',
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> borderRight: 1,
<Typography variant="h6"> borderColor: 'divider',
Chat History bgcolor: 'grey.50',
</Typography> display: 'flex',
<Box> flexDirection: 'column',
<IconButton }}>
size="small" <Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
onClick={createNewConversation} <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
title="New Conversation" <Typography variant="h6">
sx={{ mr: 0.5 }} Chat History
> </Typography>
<Add /> <Box>
</IconButton> <IconButton
<IconButton size="small"
size="small" onClick={createNewConversation}
onClick={() => setShowHistory(false)} title="New Conversation"
title="Close History" sx={{ mr: 0.5 }}
> >
<Close /> <Add />
</IconButton> </IconButton>
<IconButton
size="small"
onClick={() => setShowHistory(false)}
title="Close History"
>
<Close />
</IconButton>
</Box>
</Box>
</Box> </Box>
</Box>
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 2 }}>
{!isAuthenticated ? ( {!isAuthenticated ? (
<Box sx={{ textAlign: 'center', py: 3 }}> <Box sx={{ textAlign: 'center', py: 3 }}>
@@ -638,8 +648,242 @@ export default function FloatingChat() {
))} ))}
</Box> </Box>
)} )}
</Box>
</Box>
)}
{/* Main Chat Area - Right Side */}
<Box sx={{
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden'
}}>
{/* Suggested Questions */}
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{t('suggestions.title')}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{suggestedQuestions.slice(0, 3).map((question, index) => (
<Chip
key={index}
label={question}
size="small"
variant="outlined"
onClick={() => setInputMessage(question)}
sx={{
fontSize: '0.75rem',
cursor: 'pointer',
'&:hover': {
bgcolor: 'primary.light',
color: 'white',
},
}}
/>
))}
</Box>
</Box>
{/* Messages */}
<Box
sx={{
flexGrow: 1,
overflow: 'auto',
p: 1,
}}
>
{messages.map((message) => (
<Box
key={message.id}
sx={{
display: 'flex',
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
mb: 2,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: message.role === 'user' ? 'row-reverse' : 'row',
alignItems: 'flex-start',
maxWidth: '85%',
gap: 1,
}}
>
<Avatar
sx={{
width: 32,
height: 32,
bgcolor: message.role === 'user' ? 'primary.main' : 'secondary.main',
}}
>
{message.role === 'user' ? <Person fontSize="small" /> : <SmartToy fontSize="small" />}
</Avatar>
<Paper
elevation={1}
sx={{
p: 1.5,
bgcolor: message.role === 'user' ? 'primary.light' : 'background.paper',
color: message.role === 'user' ? 'white' : 'text.primary',
borderRadius: 2,
maxWidth: '100%',
}}
>
{message.role === 'assistant' ? (
<ReactMarkdown
components={{
p: ({ children }) => (
<Typography
variant="body2"
sx={{ mb: 1, lineHeight: 1.4 }}
>
{children}
</Typography>
),
h3: ({ children }) => (
<Typography
variant="h6"
sx={{ fontWeight: 'bold', mt: 2, mb: 1 }}
>
{children}
</Typography>
),
strong: ({ children }) => (
<Typography
component="span"
sx={{ fontWeight: 'bold' }}
>
{children}
</Typography>
),
ul: ({ children }) => (
<Box component="ul" sx={{ pl: 2, mb: 1 }}>
{children}
</Box>
),
li: ({ children }) => (
<Typography
component="li"
variant="body2"
sx={{ mb: 0.5 }}
>
{children}
</Typography>
),
}}
>
{message.content}
</ReactMarkdown>
) : (
<Typography
variant="body2"
sx={{
whiteSpace: 'pre-wrap',
lineHeight: 1.4,
}}
>
{message.content}
</Typography>
)}
{message.role === 'assistant' && (
<Box sx={{ display: 'flex', gap: 0.5, mt: 1, justifyContent: 'flex-end' }}>
<IconButton
size="small"
onClick={() => copyToClipboard(message.content)}
>
<ContentCopy fontSize="small" />
</IconButton>
<IconButton size="small">
<ThumbUp fontSize="small" />
</IconButton>
<IconButton size="small">
<ThumbDown fontSize="small" />
</IconButton>
</Box>
)}
<Typography
variant="caption"
sx={{
display: 'block',
textAlign: 'right',
mt: 0.5,
opacity: 0.7,
}}
>
{message.timestamp.toLocaleTimeString(locale === 'en' ? 'en-US' : 'ro-RO', {
hour: '2-digit',
minute: '2-digit',
})}
</Typography>
</Paper>
</Box>
</Box>
))}
{isLoading && (
<Box sx={{ display: 'flex', justifyContent: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
<Avatar sx={{ width: 32, height: 32, bgcolor: 'secondary.main' }}>
<SmartToy fontSize="small" />
</Avatar>
<Paper elevation={1} sx={{ p: 1.5, borderRadius: 2 }}>
<Typography variant="body2">
{t('loading')}
</Typography>
</Paper>
</Box>
</Box>
)}
<div ref={messagesEndRef} />
</Box>
<Divider />
{/* Input */}
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<TextField
fullWidth
size="small"
multiline
maxRows={3}
placeholder={t('placeholder')}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={isLoading}
variant="outlined"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
}
}}
/>
<Button
variant="contained"
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isLoading}
sx={{
minWidth: 'auto',
px: 2,
borderRadius: 2,
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
}}
>
<Send fontSize="small" />
</Button>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
{t('enterToSend')}
</Typography>
</Box>
</Box> </Box>
)} </Box>
{/* Conversation Menu */} {/* Conversation Menu */}
<Menu <Menu
@@ -709,230 +953,6 @@ export default function FloatingChat() {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Suggested Questions */}
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{t('suggestions.title')}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{suggestedQuestions.slice(0, 3).map((question, index) => (
<Chip
key={index}
label={question}
size="small"
variant="outlined"
onClick={() => setInputMessage(question)}
sx={{
fontSize: '0.75rem',
cursor: 'pointer',
'&:hover': {
bgcolor: 'primary.light',
color: 'white',
},
}}
/>
))}
</Box>
</Box>
{/* Messages */}
<Box
sx={{
flexGrow: 1,
overflow: 'auto',
p: 1,
}}
>
{messages.map((message) => (
<Box
key={message.id}
sx={{
display: 'flex',
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
mb: 2,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: message.role === 'user' ? 'row-reverse' : 'row',
alignItems: 'flex-start',
maxWidth: '85%',
gap: 1,
}}
>
<Avatar
sx={{
width: 32,
height: 32,
bgcolor: message.role === 'user' ? 'primary.main' : 'secondary.main',
}}
>
{message.role === 'user' ? <Person fontSize="small" /> : <SmartToy fontSize="small" />}
</Avatar>
<Paper
elevation={1}
sx={{
p: 1.5,
bgcolor: message.role === 'user' ? 'primary.light' : 'background.paper',
color: message.role === 'user' ? 'white' : 'text.primary',
borderRadius: 2,
maxWidth: '100%',
}}
>
{message.role === 'assistant' ? (
<ReactMarkdown
components={{
p: ({ children }) => (
<Typography
variant="body2"
sx={{ mb: 1, lineHeight: 1.4 }}
>
{children}
</Typography>
),
h3: ({ children }) => (
<Typography
variant="h6"
sx={{ fontWeight: 'bold', mt: 2, mb: 1 }}
>
{children}
</Typography>
),
strong: ({ children }) => (
<Typography
component="span"
sx={{ fontWeight: 'bold' }}
>
{children}
</Typography>
),
ul: ({ children }) => (
<Box component="ul" sx={{ pl: 2, mb: 1 }}>
{children}
</Box>
),
li: ({ children }) => (
<Typography
component="li"
variant="body2"
sx={{ mb: 0.5 }}
>
{children}
</Typography>
),
}}
>
{message.content}
</ReactMarkdown>
) : (
<Typography
variant="body2"
sx={{
whiteSpace: 'pre-wrap',
lineHeight: 1.4,
}}
>
{message.content}
</Typography>
)}
{message.role === 'assistant' && (
<Box sx={{ display: 'flex', gap: 0.5, mt: 1, justifyContent: 'flex-end' }}>
<IconButton
size="small"
onClick={() => copyToClipboard(message.content)}
>
<ContentCopy fontSize="small" />
</IconButton>
<IconButton size="small">
<ThumbUp fontSize="small" />
</IconButton>
<IconButton size="small">
<ThumbDown fontSize="small" />
</IconButton>
</Box>
)}
<Typography
variant="caption"
sx={{
display: 'block',
textAlign: 'right',
mt: 0.5,
opacity: 0.7,
}}
>
{message.timestamp.toLocaleTimeString(locale === 'en' ? 'en-US' : 'ro-RO', {
hour: '2-digit',
minute: '2-digit',
})}
</Typography>
</Paper>
</Box>
</Box>
))}
{isLoading && (
<Box sx={{ display: 'flex', justifyContent: 'flex-start', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
<Avatar sx={{ width: 32, height: 32, bgcolor: 'secondary.main' }}>
<SmartToy fontSize="small" />
</Avatar>
<Paper elevation={1} sx={{ p: 1.5, borderRadius: 2 }}>
<Typography variant="body2">
{t('loading')}
</Typography>
</Paper>
</Box>
</Box>
)}
<div ref={messagesEndRef} />
</Box>
<Divider />
{/* Input */}
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<TextField
fullWidth
size="small"
multiline
maxRows={3}
placeholder={t('placeholder')}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={isLoading}
variant="outlined"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
}
}}
/>
<Button
variant="contained"
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isLoading}
sx={{
minWidth: 'auto',
px: 2,
borderRadius: 2,
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
}}
>
<Send fontSize="small" />
</Button>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
{t('enterToSend')}
</Typography>
</Box>
</> </>
)} )}
</Paper> </Paper>