Files
LocalMind/components/chat/MessageContent.tsx
evandro 2a294f1f87 refactor(chat): split MessageBubble into avatar, content and code block components
MessageBubble CC 19 -> below 10. The fence and indented code block rules were identical and are now one CodeBlock component; the Markdown style map lives in markdownStyles.ts. Adds characterization tests for the bubble (roles, actions visibility, edited badge, copy feedback).
2026-10-10 04:20:59 -03:00

74 lines
2.2 KiB
TypeScript

import { useThemeColor } from '@/hooks/useThemeColor';
import { ChatMessage } from '@/store/useAppStore';
import Markdown from '@ronradtke/react-native-markdown-display';
import * as Clipboard from 'expo-clipboard';
import React, { useMemo, useState } from 'react';
import { StyleSheet, Text } from 'react-native';
import { CodeBlock } from './CodeBlock';
import { createMarkdownStyles } from './markdownStyles';
interface MessageContentProps {
message: ChatMessage;
/** True while this message is the answer currently being streamed. */
showingStreaming: boolean;
}
export function MessageContent({ message, showingStreaming }: MessageContentProps) {
const colors = useThemeColor();
const [copiedText, setCopiedText] = useState<string | null>(null);
const handleCopy = async (text: string) => {
await Clipboard.setStringAsync(text);
setCopiedText(text);
setTimeout(() => setCopiedText(null), 2000);
};
const markdownStyles = useMemo(() => createMarkdownStyles(colors), [colors]);
const markdownRules = useMemo(() => ({
fence: (node: any) => (
<CodeBlock
key={node.key}
language={node.sourceInfo || 'code'}
content={node.content || ''}
copied={copiedText === (node.content || '')}
onCopy={handleCopy}
/>
),
code_block: (node: any) => (
<CodeBlock
key={node.key}
language="code"
content={node.content || ''}
copied={copiedText === (node.content || '')}
onCopy={handleCopy}
/>
),
}), [copiedText]);
if (message.role === 'user') {
return (
<Text style={[styles.text, { color: colors.messageUserText }]}>
{message.content}
</Text>
);
}
if (!message.content && showingStreaming) {
return null; // TypingIndicator handled externally
}
return (
<Markdown style={markdownStyles} rules={markdownRules}>
{message.content}
</Markdown>
);
}
const styles = StyleSheet.create({
text: {
fontSize: 15,
lineHeight: 22,
},
});