openclaw/ui/src/styles/chat/grouped.css
Glucksberg 1353a848d7 fix(ui): visually distinguish system messages in webchat
Add distinct styling for system role messages in the webchat UI.
System messages now appear centered with muted styling, clearly
differentiated from user and assistant messages.

- Add 'system' roleClass to renderMessageGroup and renderAvatar
- Add CSS styles for .chat-group.system and .chat-avatar.system
- Use info icon (ℹ) for system message avatars

Closes #2001
2026-01-30 14:49:03 +00:00

303 lines
5.6 KiB
CSS

/* =============================================
GROUPED CHAT LAYOUT (Slack-style)
============================================= */
/* Chat Group Layout - default (assistant/other on left) */
.chat-group {
display: flex;
gap: 12px;
align-items: flex-start;
margin-bottom: 16px;
margin-left: 4px;
margin-right: 16px;
}
/* User messages on right */
.chat-group.user {
flex-direction: row-reverse;
justify-content: flex-start;
}
.chat-group-messages {
display: flex;
flex-direction: column;
gap: 2px;
max-width: min(900px, calc(100% - 60px));
}
/* User messages align content right */
.chat-group.user .chat-group-messages {
align-items: flex-end;
}
.chat-group.user .chat-group-footer {
justify-content: flex-end;
}
/* System messages centered with muted styling */
.chat-group.system {
justify-content: center;
margin-left: 16px;
}
.chat-group.system .chat-group-messages {
align-items: center;
max-width: min(600px, calc(100% - 60px));
}
.chat-group.system .chat-bubble {
background: var(--secondary);
border: 1px solid var(--border);
font-size: 13px;
color: var(--muted);
padding: 8px 12px;
}
.chat-group.system .chat-group-footer {
justify-content: center;
}
.chat-group.system .chat-sender-name {
color: var(--muted);
opacity: 0.8;
}
/* Footer at bottom of message group (role + time) */
.chat-group-footer {
display: flex;
gap: 8px;
align-items: baseline;
margin-top: 6px;
}
.chat-sender-name {
font-weight: 500;
font-size: 12px;
color: var(--muted);
}
.chat-group-timestamp {
font-size: 11px;
color: var(--muted);
opacity: 0.7;
}
/* Avatar Styles */
.chat-avatar {
width: 40px;
height: 40px;
border-radius: 8px;
background: var(--panel-strong);
display: grid;
place-items: center;
font-weight: 600;
font-size: 14px;
flex-shrink: 0;
align-self: flex-end; /* Align with last message in group */
margin-bottom: 4px; /* Optical alignment */
}
.chat-avatar.user {
background: var(--accent-subtle);
color: var(--accent);
}
.chat-avatar.assistant {
background: var(--secondary);
color: var(--muted);
}
.chat-avatar.other {
background: var(--secondary);
color: var(--muted);
}
.chat-avatar.tool {
background: var(--secondary);
color: var(--muted);
}
.chat-avatar.system {
background: var(--border);
color: var(--muted);
font-size: 16px;
}
/* Image avatar support */
img.chat-avatar {
display: block;
object-fit: cover;
object-position: center;
}
/* Minimal Bubble Design - dynamic width based on content */
.chat-bubble {
position: relative;
display: inline-block;
border: 1px solid transparent;
background: var(--card);
border-radius: var(--radius-lg);
padding: 10px 14px;
box-shadow: none;
transition: background 150ms ease-out, border-color 150ms ease-out;
max-width: 100%;
word-wrap: break-word;
}
.chat-bubble.has-copy {
padding-right: 36px;
}
.chat-copy-btn {
position: absolute;
top: 6px;
right: 8px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--muted);
border-radius: var(--radius-md);
padding: 4px 6px;
font-size: 14px;
line-height: 1;
cursor: pointer;
opacity: 0;
pointer-events: none;
transition: opacity 120ms ease-out, background 120ms ease-out;
}
.chat-copy-btn__icon {
display: inline-flex;
width: 14px;
height: 14px;
position: relative;
}
.chat-copy-btn__icon svg {
width: 14px;
height: 14px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.chat-copy-btn__icon-copy,
.chat-copy-btn__icon-check {
position: absolute;
top: 0;
left: 0;
transition: opacity 150ms ease;
}
.chat-copy-btn__icon-check {
opacity: 0;
}
.chat-copy-btn[data-copied="1"] .chat-copy-btn__icon-copy {
opacity: 0;
}
.chat-copy-btn[data-copied="1"] .chat-copy-btn__icon-check {
opacity: 1;
}
.chat-bubble:hover .chat-copy-btn {
opacity: 1;
pointer-events: auto;
}
.chat-copy-btn:hover {
background: var(--bg-hover);
}
.chat-copy-btn[data-copying="1"] {
opacity: 0;
pointer-events: none;
}
.chat-copy-btn[data-error="1"] {
opacity: 1;
pointer-events: auto;
border-color: var(--danger-subtle);
background: var(--danger-subtle);
color: var(--danger);
}
.chat-copy-btn[data-copied="1"] {
opacity: 1;
pointer-events: auto;
border-color: var(--ok-subtle);
background: var(--ok-subtle);
color: var(--ok);
}
.chat-copy-btn:focus-visible {
opacity: 1;
pointer-events: auto;
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (hover: none) {
.chat-copy-btn {
opacity: 1;
pointer-events: auto;
}
}
/* Light mode: restore borders */
:root[data-theme="light"] .chat-bubble {
border-color: var(--border);
box-shadow: inset 0 1px 0 var(--card-highlight);
}
.chat-bubble:hover {
background: var(--bg-hover);
}
/* User bubbles have different styling */
.chat-group.user .chat-bubble {
background: var(--accent-subtle);
border-color: transparent;
}
:root[data-theme="light"] .chat-group.user .chat-bubble {
border-color: rgba(234, 88, 12, 0.2);
background: rgba(251, 146, 60, 0.12);
}
.chat-group.user .chat-bubble:hover {
background: rgba(255, 77, 77, 0.15);
}
/* Streaming animation */
.chat-bubble.streaming {
animation: pulsing-border 1.5s ease-out infinite;
}
@keyframes pulsing-border {
0%, 100% {
border-color: var(--border);
}
50% {
border-color: var(--accent);
}
}
/* Fade-in animation for new messages */
.chat-bubble.fade-in {
animation: fade-in 200ms ease-out;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}