changed styling and organization of messages
This commit is contained in:
parent
aa5c321f09
commit
1e46d8f93e
5 changed files with 506 additions and 273 deletions
|
|
@ -1,9 +1,7 @@
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { FaDownload } from "react-icons/fa";
|
|
||||||
import { MdOutlineRemoveRedEye } from "react-icons/md";
|
|
||||||
import { useFileDownload } from "../../../hooks/useWorkflows";
|
import { useFileDownload } from "../../../hooks/useWorkflows";
|
||||||
import { useLanguage } from "../../../contexts/LanguageContext";
|
|
||||||
import { Message, Document } from "./dashboardChatAreaTypes";
|
import { Message, Document } from "./dashboardChatAreaTypes";
|
||||||
|
import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css';
|
||||||
|
|
||||||
interface MessageItemProps {
|
interface MessageItemProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
|
|
@ -61,9 +59,8 @@ const getFileIcon = (type?: string, ext?: string): string => {
|
||||||
return '📄';
|
return '📄';
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageItem: React.FC<MessageItemProps> = ({ message, index, onFilePreview }) => {
|
const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) => {
|
||||||
const { t } = useLanguage();
|
const { downloadFile, isDownloading } = useFileDownload();
|
||||||
const { downloadFile, isDownloading, error: downloadError } = useFileDownload();
|
|
||||||
|
|
||||||
// Debug: Log what the MessageItem is receiving
|
// Debug: Log what the MessageItem is receiving
|
||||||
console.log(`🎭 MessageItem rendering:`, {
|
console.log(`🎭 MessageItem rendering:`, {
|
||||||
|
|
@ -75,7 +72,11 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index, onFilePreview
|
||||||
hasDocuments: !!(message.documents),
|
hasDocuments: !!(message.documents),
|
||||||
documentsArray: message.documents,
|
documentsArray: message.documents,
|
||||||
documentsLength: message.documents?.length || 0,
|
documentsLength: message.documents?.length || 0,
|
||||||
documentsCheck: message.documents && message.documents.length > 0
|
documentsCheck: message.documents && message.documents.length > 0,
|
||||||
|
// Timestamp debugging
|
||||||
|
timestamp: message.timestamp,
|
||||||
|
hasTimestamp: !!message.timestamp,
|
||||||
|
timestampType: typeof message.timestamp
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDocumentClick = (document: Document) => {
|
const handleDocumentClick = (document: Document) => {
|
||||||
|
|
@ -149,134 +150,120 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index, onFilePreview
|
||||||
console.log(`📭 No documents to render for message ${message.id}`);
|
console.log(`📭 No documents to render for message ${message.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format timestamp
|
||||||
|
const formatTimestamp = (timestamp?: string) => {
|
||||||
|
console.log(`⏰ formatTimestamp called with:`, { timestamp, type: typeof timestamp, hasValue: !!timestamp });
|
||||||
|
|
||||||
|
if (!timestamp) {
|
||||||
|
console.log(`⏰ No timestamp provided, returning empty string`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
console.log(`⏰ Parsed date:`, { date, isValid: !isNaN(date.getTime()) });
|
||||||
|
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
console.log(`⏰ Invalid date, returning empty string`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const isToday = date.toDateString() === now.toDateString();
|
||||||
|
|
||||||
|
let formatted = '';
|
||||||
|
if (isToday) {
|
||||||
|
formatted = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
} else {
|
||||||
|
formatted = date.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' +
|
||||||
|
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`⏰ Formatted timestamp:`, { formatted });
|
||||||
|
return formatted;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
className={`${messageStyles.message_item} ${
|
||||||
padding: '12px',
|
message.role === 'user' ? messageStyles.user : messageStyles.assistant
|
||||||
borderRadius: '8px',
|
}`}
|
||||||
backgroundColor: message.role === 'user'
|
|
||||||
? 'var(--color-secondary-disabled)'
|
|
||||||
: 'var(--color-surface)',
|
|
||||||
marginBottom: '8px'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{
|
<div className={messageStyles.message_header}>
|
||||||
fontSize: '12px',
|
|
||||||
color: 'var(--color-gray)',
|
|
||||||
marginBottom: '4px',
|
|
||||||
fontWeight: '500'
|
|
||||||
}}>
|
|
||||||
{message.role === 'user' ? 'You' : message.agentName}
|
{message.role === 'user' ? 'You' : message.agentName}
|
||||||
{message.timestamp && ` • ${new Date(message.timestamp).toLocaleTimeString()}`}
|
{message.timestamp && (
|
||||||
</div>
|
<span className={messageStyles.message_timestamp_inline}>
|
||||||
|
• {formatTimestamp(message.timestamp)}
|
||||||
<div style={{
|
|
||||||
lineHeight: '1.5',
|
|
||||||
whiteSpace: 'pre-wrap'
|
|
||||||
}}>
|
|
||||||
{message.content || (
|
|
||||||
<span style={{
|
|
||||||
color: 'var(--color-gray)',
|
|
||||||
fontStyle: 'italic'
|
|
||||||
}}>
|
|
||||||
[No message content]
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasDocuments && (
|
<div className={`${messageStyles.message_bubble} ${
|
||||||
<div style={{
|
message.role === 'user' ? messageStyles.user : messageStyles.assistant
|
||||||
marginTop: '12px',
|
}`}>
|
||||||
padding: '8px',
|
<div className={messageStyles.message_content}>
|
||||||
backgroundColor: 'var(--color-bg)',
|
{message.content || (
|
||||||
borderRadius: '6px',
|
<span className={messageStyles.message_no_content}>
|
||||||
border: '1px solid var(--color-gray-disabled)'
|
[No message content]
|
||||||
}}>
|
</span>
|
||||||
<div style={{
|
)}
|
||||||
fontSize: '12px',
|
</div>
|
||||||
color: 'var(--color-gray)',
|
|
||||||
marginBottom: '8px',
|
{hasDocuments && (
|
||||||
fontWeight: '500'
|
<div className={`${messageStyles.message_documents} ${
|
||||||
}}>
|
message.role === 'user' ? messageStyles.user : messageStyles.assistant
|
||||||
📎 Attached Files ({message.documents!.length})
|
}`}>
|
||||||
</div>
|
<div className={`${messageStyles.message_documents_header} ${
|
||||||
<div>
|
message.role === 'user' ? messageStyles.user : messageStyles.assistant
|
||||||
{message.documents!.map((document, docIndex) => {
|
}`}>
|
||||||
console.log(`📄 Rendering document ${docIndex + 1}:`, document);
|
📎 {message.role === 'user' ? 'Uploaded' : 'Attached'} Files ({message.documents!.length})
|
||||||
return (
|
</div>
|
||||||
<div
|
<div>
|
||||||
key={document.id || docIndex}
|
{message.documents!.map((document, docIndex) => {
|
||||||
style={{
|
console.log(`📄 Rendering document ${docIndex + 1}:`, document);
|
||||||
display: 'flex',
|
return (
|
||||||
alignItems: 'center',
|
<div
|
||||||
gap: '8px',
|
key={document.id || docIndex}
|
||||||
padding: '6px',
|
className={messageStyles.message_document_item}
|
||||||
borderRadius: '4px',
|
onClick={() => handleDocumentClick(document)}
|
||||||
backgroundColor: 'var(--color-surface)',
|
title={`Click to open ${document.name}`}
|
||||||
marginBottom: '4px',
|
>
|
||||||
cursor: 'pointer'
|
<span className={messageStyles.message_document_icon}>
|
||||||
}}
|
{getFileIcon(document.type, document.ext)}
|
||||||
onClick={() => handleDocumentClick(document)}
|
</span>
|
||||||
title={`Click to open ${document.name}`}
|
<div className={messageStyles.message_document_info}>
|
||||||
>
|
<div className={messageStyles.message_document_name}>
|
||||||
<span style={{ fontSize: '16px' }}>
|
{document.ext ? `${document.name}.${document.ext}` : document.name}
|
||||||
{getFileIcon(document.type, document.ext)}
|
</div>
|
||||||
</span>
|
{document.size && (
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div className={messageStyles.message_document_size}>
|
||||||
<div style={{
|
{formatFileSize(document.size)}
|
||||||
fontSize: '14px',
|
</div>
|
||||||
overflow: 'hidden',
|
)}
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
}}>
|
|
||||||
{document.ext ? `${document.name}.${document.ext}` : document.name}
|
|
||||||
</div>
|
|
||||||
{document.size && (
|
|
||||||
<div style={{
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'var(--color-gray)'
|
|
||||||
}}>
|
|
||||||
{formatFileSize(document.size)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className={messageStyles.message_document_actions}>
|
||||||
</div>
|
<button
|
||||||
<div style={{ display: 'flex', gap: '4px' }}>
|
onClick={(e) => handlePreview(document, e)}
|
||||||
<button
|
className={messageStyles.message_document_action}
|
||||||
onClick={(e) => handlePreview(document, e)}
|
title="Preview file"
|
||||||
style={{
|
>
|
||||||
padding: '4px 8px',
|
👁️
|
||||||
fontSize: '12px',
|
</button>
|
||||||
backgroundColor: 'transparent',
|
<button
|
||||||
border: '1px solid var(--color-gray-disabled)',
|
onClick={(e) => handleDownload(document, e)}
|
||||||
borderRadius: '4px',
|
disabled={isDownloading}
|
||||||
cursor: 'pointer'
|
className={messageStyles.message_document_action}
|
||||||
}}
|
title="Download file"
|
||||||
title="Preview file"
|
>
|
||||||
>
|
⬇️
|
||||||
👁️
|
</button>
|
||||||
</button>
|
</div>
|
||||||
<button
|
</div>
|
||||||
onClick={(e) => handleDownload(document, e)}
|
);
|
||||||
disabled={isDownloading}
|
})}
|
||||||
style={{
|
</div>
|
||||||
padding: '4px 8px',
|
</div>
|
||||||
fontSize: '12px',
|
)}
|
||||||
backgroundColor: 'transparent',
|
</div>
|
||||||
border: '1px solid var(--color-gray-disabled)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
title="Download file"
|
|
||||||
>
|
|
||||||
⬇️
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { useApiRequest } from '../../../hooks/useApi';
|
import { useApiRequest } from '../../../hooks/useApi';
|
||||||
import MessageItem from './DashboardChatAreaMessageItem';
|
import MessageItem from './DashboardChatAreaMessageItem';
|
||||||
import { WorkflowMessage, Document, WorkflowState } from './dashboardChatAreaTypes';
|
import { WorkflowMessage, Document, WorkflowState } from './dashboardChatAreaTypes';
|
||||||
|
import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css';
|
||||||
|
|
||||||
interface MessageListProps {
|
interface MessageListProps {
|
||||||
workflowState: WorkflowState;
|
workflowState: WorkflowState;
|
||||||
|
|
@ -17,7 +18,7 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
|
||||||
// Use documents directly from the API
|
// Use documents directly from the API
|
||||||
documents = workflowMessage.documents.map(doc => ({
|
documents = workflowMessage.documents.map(doc => ({
|
||||||
id: doc.id || doc.fileId,
|
id: doc.id || doc.fileId,
|
||||||
fileId: parseInt(doc.fileId),
|
fileId: typeof doc.fileId === 'string' ? parseInt(doc.fileId) : doc.fileId,
|
||||||
name: doc.filename,
|
name: doc.filename,
|
||||||
ext: doc.filename.split('.').pop() || 'unknown',
|
ext: doc.filename.split('.').pop() || 'unknown',
|
||||||
type: doc.mimeType,
|
type: doc.mimeType,
|
||||||
|
|
@ -77,11 +78,12 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
|
||||||
role: workflowMessage.role,
|
role: workflowMessage.role,
|
||||||
agentName: workflowMessage.role === 'user' ? 'You' : 'Assistant',
|
agentName: workflowMessage.role === 'user' ? 'You' : 'Assistant',
|
||||||
content: possibleContent,
|
content: possibleContent,
|
||||||
timestamp: workflowMessage.timestamp,
|
timestamp: workflowMessage.publishedAt || workflowMessage.timestamp,
|
||||||
documents: documents
|
documents: documents
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`🔄 Transformation result for ${workflowMessage.id}:`, {
|
console.log(`🔄 Transformation result for ${workflowMessage.id}:`, {
|
||||||
|
role: workflowMessage.role,
|
||||||
originalMessage: workflowMessage,
|
originalMessage: workflowMessage,
|
||||||
originalContent: workflowMessage.content,
|
originalContent: workflowMessage.content,
|
||||||
originalContentType: typeof workflowMessage.content,
|
originalContentType: typeof workflowMessage.content,
|
||||||
|
|
@ -89,6 +91,13 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
|
||||||
possibleContentType: typeof possibleContent,
|
possibleContentType: typeof possibleContent,
|
||||||
transformedContent: transformedMessage.content,
|
transformedContent: transformedMessage.content,
|
||||||
transformedContentType: typeof transformedMessage.content,
|
transformedContentType: typeof transformedMessage.content,
|
||||||
|
documentsCount: documents.length,
|
||||||
|
hasDocuments: documents.length > 0,
|
||||||
|
// Timestamp debugging
|
||||||
|
publishedAt: workflowMessage.publishedAt,
|
||||||
|
timestamp: workflowMessage.timestamp,
|
||||||
|
finalTimestamp: transformedMessage.timestamp,
|
||||||
|
hasTimestamp: !!transformedMessage.timestamp,
|
||||||
allOriginalKeys: Object.keys(workflowMessage),
|
allOriginalKeys: Object.keys(workflowMessage),
|
||||||
// Check for alternative field names
|
// Check for alternative field names
|
||||||
alternativeFields: {
|
alternativeFields: {
|
||||||
|
|
@ -99,6 +108,14 @@ const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, reques
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Special logging for user messages with documents
|
||||||
|
if (workflowMessage.role === 'user' && documents.length > 0) {
|
||||||
|
console.log(`👤📎 USER message with ${documents.length} documents:`, {
|
||||||
|
messageId: workflowMessage.id,
|
||||||
|
documents: documents.map(doc => ({ name: doc.name, fileId: doc.fileId, type: doc.type }))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return transformedMessage;
|
return transformedMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -212,76 +229,23 @@ const MessageList: React.FC<MessageListProps> = ({
|
||||||
}
|
}
|
||||||
}, [transformedMessages.length, scrollToBottom]);
|
}, [transformedMessages.length, scrollToBottom]);
|
||||||
|
|
||||||
const { currentWorkflowId, workflow, isLoading, error } = workflowState;
|
const { currentWorkflowId, isLoading, error } = workflowState;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={messageStyles.message_list_container}>
|
||||||
{/* Add CSS animations */}
|
<div
|
||||||
<style>
|
ref={scrollContainerRef}
|
||||||
{`
|
className={messageStyles.chat_messages}
|
||||||
@keyframes pulse {
|
onScroll={checkScrollPosition}
|
||||||
0%, 100% { opacity: 0.7; }
|
>
|
||||||
50% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-to-bottom-btn:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-to-bottom-btn:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
<div
|
|
||||||
ref={scrollContainerRef}
|
|
||||||
style={{
|
|
||||||
padding: '16px',
|
|
||||||
height: '100%',
|
|
||||||
overflow: 'auto'
|
|
||||||
}}
|
|
||||||
onScroll={checkScrollPosition}
|
|
||||||
>
|
|
||||||
{error && (
|
{error && (
|
||||||
<div style={{
|
<div className={messageStyles.message_error}>
|
||||||
padding: '8px',
|
|
||||||
backgroundColor: 'var(--color-error)',
|
|
||||||
color: 'white',
|
|
||||||
borderRadius: '4px',
|
|
||||||
marginBottom: '16px'
|
|
||||||
}}>
|
|
||||||
Error: {error}
|
Error: {error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{workflow && (
|
|
||||||
<div style={{
|
|
||||||
padding: '8px',
|
|
||||||
backgroundColor: 'var(--color-surface)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
marginBottom: '16px'
|
|
||||||
}}>
|
|
||||||
<strong>Workflow:</strong> {workflow.id}
|
|
||||||
<br />
|
|
||||||
<strong>Status:</strong> {workflow.status}
|
|
||||||
{workflow.currentRound && ` (Round ${workflow.currentRound})`}
|
|
||||||
{/* Show polling indicator */}
|
|
||||||
{workflow && ['running', 'processing', 'started'].includes(workflow.status) && (
|
|
||||||
<span style={{
|
|
||||||
marginLeft: '8px',
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'var(--color-secondary)',
|
|
||||||
opacity: 0.7,
|
|
||||||
animation: 'pulse 2s infinite'
|
|
||||||
}}>
|
|
||||||
🔄 Live updates (2s)
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
||||||
|
<div className={messageStyles.messages_container}>
|
||||||
{transformedMessages.map((message, index) => {
|
{transformedMessages.map((message, index) => {
|
||||||
console.log(`🎨 Rendering transformed message ${message.id}:`, {
|
console.log(`🎨 Rendering transformed message ${message.id}:`, {
|
||||||
role: message.role,
|
role: message.role,
|
||||||
|
|
@ -302,58 +266,38 @@ const MessageList: React.FC<MessageListProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(isLoading || isTransforming) && (
|
{(isLoading || isTransforming) && (
|
||||||
<div style={{ textAlign: 'center', padding: '16px' }}>
|
<div className={messageStyles.message_loading}>
|
||||||
<p style={{ color: 'var(--color-gray)' }}>
|
{isTransforming ? 'Processing messages...' : 'Loading messages...'}
|
||||||
{isTransforming ? 'Processing messages...' : 'Loading messages...'}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{transformedMessages.length === 0 && !isLoading && !isTransforming && !currentWorkflowId && (
|
{transformedMessages.length === 0 && !isLoading && !isTransforming && !currentWorkflowId && (
|
||||||
<p style={{ color: 'var(--color-gray)', textAlign: 'center' }}>
|
<div className={messageStyles.message_empty_state}>
|
||||||
No workflow selected. Start a conversation to create a new workflow.
|
No workflow selected. Start a conversation to create a new workflow.
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{transformedMessages.length === 0 && !isLoading && !isTransforming && currentWorkflowId && (
|
{transformedMessages.length === 0 && !isLoading && !isTransforming && currentWorkflowId && (
|
||||||
<p style={{ color: 'var(--color-gray)', textAlign: 'center' }}>
|
<div className={messageStyles.message_empty_state}>
|
||||||
No messages in this workflow yet.
|
No messages in this workflow yet.
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scroll to bottom button - positioned relative to message list container */}
|
||||||
|
<button
|
||||||
|
className={`${messageStyles.scroll_to_bottom_btn} ${
|
||||||
|
(isUserScrolledUp && transformedMessages.length > 0)
|
||||||
|
? messageStyles.visible
|
||||||
|
: messageStyles.hidden
|
||||||
|
}`}
|
||||||
|
onClick={scrollToBottom}
|
||||||
|
title="Scroll to bottom"
|
||||||
|
>
|
||||||
|
⬇️
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scroll to bottom button - positioned relative to parent container, not scrollable area */}
|
|
||||||
<button
|
|
||||||
className="scroll-to-bottom-btn"
|
|
||||||
onClick={scrollToBottom}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '20px',
|
|
||||||
right: '20px',
|
|
||||||
backgroundColor: 'var(--color-secondary)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '50%',
|
|
||||||
width: '48px',
|
|
||||||
height: '48px',
|
|
||||||
fontSize: '20px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
|
||||||
zIndex: 1000,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
opacity: (isUserScrolledUp && transformedMessages.length > 0) ? 1 : 0,
|
|
||||||
visibility: (isUserScrolledUp && transformedMessages.length > 0) ? 'visible' : 'hidden',
|
|
||||||
transition: 'opacity 0.3s ease-in-out, visibility 0.3s ease-in-out, transform 0.2s ease-in-out',
|
|
||||||
pointerEvents: (isUserScrolledUp && transformedMessages.length > 0) ? 'auto' : 'none'
|
|
||||||
}}
|
|
||||||
title="Scroll to bottom"
|
|
||||||
>
|
|
||||||
⬇️
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,46 +56,7 @@
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Messages */
|
/* Chat Messages styles moved to DashboardChatMessages.module.css */
|
||||||
.chat_messages {
|
|
||||||
flex: 1;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat_messages::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat_messages::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat_messages::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--color-gray-disabled);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat_messages::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages_container {
|
|
||||||
min-height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages_spacer {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat Input */
|
/* Chat Input */
|
||||||
.chat_input {
|
.chat_input {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,341 @@
|
||||||
|
/* Message-specific styles for DashboardChat components */
|
||||||
|
|
||||||
|
/* Message List Container - wraps the entire message list and positions scroll button */
|
||||||
|
.message_list_container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chat Messages Scrollable Area */
|
||||||
|
.chat_messages {
|
||||||
|
flex: 1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat_messages::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat_messages::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat_messages::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-gray-disabled);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat_messages::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Messages Container */
|
||||||
|
.messages_container {
|
||||||
|
min-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages_spacer {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message Item Styles - Modern Chat Layout */
|
||||||
|
.message_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 80%;
|
||||||
|
min-width: 10%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design for smaller screens */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.message_item {
|
||||||
|
max-width: 85%;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.message_item {
|
||||||
|
max-width: 95%;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User messages - right aligned */
|
||||||
|
.message_item.user {
|
||||||
|
align-self: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assistant messages - left aligned */
|
||||||
|
.message_item.assistant {
|
||||||
|
align-self: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_header {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_bubble {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 18px;
|
||||||
|
position: relative;
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User message bubble */
|
||||||
|
.message_bubble.user {
|
||||||
|
background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-hover) 100%);
|
||||||
|
color: white;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assistant message bubble */
|
||||||
|
.message_bubble.assistant {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: 1px solid var(--color-gray-disabled);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_content {
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_timestamp {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
margin-top: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_timestamp_inline {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_no_content {
|
||||||
|
color: var(--color-gray);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document/File Attachments in Messages */
|
||||||
|
.message_documents {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document styling for user messages */
|
||||||
|
.message_documents.user {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document styling for assistant messages */
|
||||||
|
.message_documents.assistant {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
border: 1px solid var(--color-gray-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_documents_header {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document header styling for user messages */
|
||||||
|
.message_documents_header.user {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Document header styling for assistant messages */
|
||||||
|
.message_documents_header.assistant {
|
||||||
|
color: var(--color-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_name {
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_size {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_action {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--color-gray-disabled);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_action:hover {
|
||||||
|
background-color: var(--color-gray-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_document_action:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll to Bottom Button */
|
||||||
|
.scroll_to_bottom_btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out, transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll_to_bottom_btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll_to_bottom_btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll_to_bottom_btn.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll_to_bottom_btn.visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading and Error States */
|
||||||
|
.message_loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
color: var(--color-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_error {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--color-error);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_empty_state {
|
||||||
|
color: var(--color-gray);
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Workflow Status Display */
|
||||||
|
.workflow_status {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow_status_title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow_status_polling {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for polling indicator */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.7; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.polling_indicator {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
.verticalDivider {
|
.verticalDivider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(86px + 20px + 1px); /* pageHeader height + gap + horizontalDivider height */
|
top: calc(86px + 23px + 1px); /* pageHeader height + gap + horizontalDivider height */
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: calc(66.666% - 9px);
|
left: calc(66.666% - 9px);
|
||||||
width: 1px;
|
width: 1px;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue