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).
This commit is contained in:
1 parent
434cba2a69
commit
2a294f1f87
6 files changed
+465
-260
No files matched your search
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user