Spaces:
Sleeping
Sleeping
| import { useConversation } from '../context/ConversationContext'; | |
| import { getChildren } from '../utils/tree'; | |
| export default function MessageBubble({ node, onBranch }) { | |
| const { state } = useConversation(); | |
| const conv = state.conversations[state.activeConversationId]; | |
| const childCount = conv ? getChildren(conv.nodes, node.id).length : 0; | |
| return ( | |
| <> | |
| {/* User message */} | |
| <div className="message-row user"> | |
| <div className="message-content"> | |
| <div className="message-bubble user">{node.userMessage}</div> | |
| <div className="message-actions" style={{ justifyContent: 'flex-end' }}> | |
| {childCount > 1 && ( | |
| <span className="branch-count" title={`${childCount} branches`}> | |
| ⑂ {childCount} | |
| </span> | |
| )} | |
| <button | |
| className="branch-btn" | |
| onClick={() => onBranch(node)} | |
| title="Create a branch from this point" | |
| > | |
| ⑂ Branch | |
| </button> | |
| </div> | |
| </div> | |
| <div className="message-avatar user">U</div> | |
| </div> | |
| {/* Assistant message */} | |
| <div className="message-row assistant"> | |
| <div className="message-avatar assistant">AI</div> | |
| <div className="message-content"> | |
| <div | |
| className={`message-bubble assistant ${ | |
| node.status === 'generating' ? 'generating' : '' | |
| } ${node.status === 'error' ? 'error' : ''}`} | |
| > | |
| {node.status === 'generating' && !node.assistantMessage && ( | |
| <span style={{ color: 'var(--text-tertiary)' }}> | |
| <span className="spinner" style={{ marginRight: 8 }} /> | |
| Generating… | |
| </span> | |
| )} | |
| {node.status === 'error' && ( | |
| <span style={{ color: 'var(--status-error)' }}> | |
| ❌ Error generating response | |
| </span> | |
| )} | |
| {node.assistantMessage && ( | |
| <span | |
| dangerouslySetInnerHTML={{ | |
| __html: formatMessage(node.assistantMessage), | |
| }} | |
| /> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| } | |
| /** | |
| * Very lightweight markdown-ish formatting. | |
| * Converts **bold**, *italic*, `code`, and newlines. | |
| */ | |
| function formatMessage(text) { | |
| return text | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/\*(.+?)\*/g, '<em>$1</em>') | |
| .replace(/`([^`]+)`/g, '<code style="background:rgba(255,255,255,0.06);padding:1px 5px;border-radius:3px;font-size:12px;">$1</code>') | |
| .replace(/\n/g, '<br/>'); | |
| } | |