changed styling and organization of messages

This commit is contained in:
Ida Dittrich 2025-08-20 16:21:13 +02:00
parent aa5c321f09
commit 1e46d8f93e
5 changed files with 506 additions and 273 deletions

View file

@ -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>
); );
}; };

View file

@ -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> </div>
{/* Scroll to bottom button - positioned relative to parent container, not scrollable area */} {/* Scroll to bottom button - positioned relative to message list container */}
<button <button
className="scroll-to-bottom-btn" className={`${messageStyles.scroll_to_bottom_btn} ${
onClick={scrollToBottom} (isUserScrolledUp && transformedMessages.length > 0)
style={{ ? messageStyles.visible
position: 'absolute', : messageStyles.hidden
bottom: '20px', }`}
right: '20px', onClick={scrollToBottom}
backgroundColor: 'var(--color-secondary)', title="Scroll to bottom"
color: 'white', >
border: 'none',
borderRadius: '50%', </button>
width: '48px', </div>
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>
</>
); );
}; };

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;