92 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
};
|