ui-nyla/src/components/Chat/ChatMessageList.tsx
2026-04-11 19:44:52 +02:00

92 lines
2.6 KiB
TypeScript

/**
* ChatMessageList -- Shared chat message display component.
*
* Renders a scrollable list of messages with Markdown support.
* Used by both the Workspace ChatStream and the Editor ChatPanel.
*/
import React, { useRef, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { useLanguage } from '../../providers/language/LanguageContext';
export interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp?: number;
}
interface ChatMessageListProps {
messages: ChatMessage[];
isProcessing?: boolean;
emptyMessage?: string;
style?: React.CSSProperties;
}
const _roleColors: Record<string, string> = {
user: 'var(--color-primary, #2563eb)',
assistant: 'var(--text-primary, #333)',
system: 'var(--text-secondary, #888)',
};
export const ChatMessageList: React.FC<ChatMessageListProps> = ({
messages,
isProcessing,
emptyMessage,
style,
}) => {
const { t } = useLanguage();
const resolvedEmpty = emptyMessage ?? t('Noch keine Nachrichten.');
const bottomRef = useRef<HTMLDivElement>(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div
style={{
flex: 1,
minHeight: 0,
overflowY: 'auto',
padding: '12px 16px',
display: 'flex',
flexDirection: 'column',
gap: 8,
...style,
}}
>
{messages.length === 0 && (
<div style={{ color: 'var(--text-secondary, #888)', fontSize: '13px', textAlign: 'center', marginTop: '24px' }}>
{resolvedEmpty}
</div>
)}
{messages.map((msg) => (
<div
key={msg.id}
style={{
padding: '8px 12px',
borderRadius: '8px',
background: msg.role === 'user' ? 'var(--bg-secondary, #f5f5f5)' : 'transparent',
fontSize: '13px',
lineHeight: 1.5,
color: _roleColors[msg.role] || 'var(--text-primary, #333)',
}}
>
<div style={{ fontWeight: 600, fontSize: '11px', marginBottom: '4px', textTransform: 'uppercase', color: 'var(--text-secondary, #888)' }}>
{msg.role}
</div>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{msg.content}
</ReactMarkdown>
</div>
))}
{isProcessing && (
<div style={{ color: 'var(--text-secondary, #888)', fontSize: '12px', fontStyle: 'italic' }}>
{t('Wird verarbeitet…')}
</div>
)}
<div ref={bottomRef} />
</div>
);
};