All files / src/components/chat MessageBubble.tsx

100% Statements 80/80
96.55% Branches 28/29
100% Functions 7/7
100% Lines 80/80

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 1071x   1x           1x 57x   57x 57x 57x 57x 57x 57x   57x 57x 57x   57x 57x 57x 57x 57x 57x   57x 21x 14x 1x 21x   57x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x   57x 57x 57x 57x 57x 57x   57x 57x 5x   57x 57x   57x 25x 25x 25x 25x 25x   25x 21x 21x 21x 21x 21x   21x 21x 21x     25x 14x 14x 14x   25x   25x 7x 7x 7x 7x   7x 7x 7x   7x 2x   7x   25x   57x   57x
import React, { useState } from 'react';
import type { Message } from './types';
import { SourceList } from './SourceList';
 
interface MessageBubbleProps {
  message: Message;
}
 
export const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
  const [showSources, setShowSources] = useState(false);
 
  const formatTime = (timestamp: Date): string => {
    return timestamp.toLocaleTimeString('zh-CN', { 
      hour: '2-digit', 
      minute: '2-digit' 
    });
  };
 
  const getSenderName = (sender: string): string => {
    return sender === 'user' ? '您' : '家庭智慧助手';
  };
 
  const getMessageTypeClass = (type: string): string => {
    switch (type) {
      case 'error': return 'error-message';
      default: return '';
    }
  };
 
  const getConfidenceColor = (confidence: number): string => {
    if (confidence >= 0.8) return '#10b981'; // Green
    if (confidence >= 0.6) return '#f59e0b'; // Yellow
    return '#ef4444'; // Red
  };
 
  const getQueryTypeLabel = (queryType: string): string => {
    const labels: { [key: string]: string } = {
      'memory_discovery': '记忆发现',
      'health_pattern': '健康模式',
      'event_planning': '事件规划',
      'cultural_heritage': '文化传承',
      'relationship_discovery': '关系发现',
      'general': '一般问题'
    };
    return labels[queryType] || queryType;
  };
 
  return (
    <div className={`message-bubble ${message.sender}-message ${getMessageTypeClass(message.type)}`}>
      <div className="message-header">
        <span className="sender-name">{getSenderName(message.sender)}</span>
        <span className="message-time">{formatTime(message.timestamp)}</span>
      </div>
      
      <div className="message-content">
        {message.type === 'error' && (
          <div className="error-icon">⚠️</div>
        )}
        <p className="message-text">{message.content}</p>
      </div>
 
      {message.sender === 'ai' && message.metadata && (
        <div className="message-metadata">
          <div className="metadata-row">
            <span className="metadata-item">
              <strong>类型:</strong> {getQueryTypeLabel(message.metadata.queryType || 'general')}
            </span>
            
            {message.metadata.confidence !== undefined && (
              <span className="metadata-item">
                <strong>置信度:</strong> 
                <span 
                  className="confidence-score"
                  style={{ color: getConfidenceColor(message.metadata.confidence) }}
                >
                  {(message.metadata.confidence * 100).toFixed(0)}%
                </span>
              </span>
            )}
            
            {message.metadata.processingTime !== undefined && (
              <span className="metadata-item">
                <strong>处理时间:</strong> {message.metadata.processingTime.toFixed(2)}s
              </span>
            )}
          </div>
 
          {message.metadata.sources && message.metadata.sources.length > 0 && (
            <div className="sources-section">
              <button 
                className="sources-toggle"
                onClick={() => setShowSources(!showSources)}
              >
                <span className="sources-icon">📚</span>
                {showSources ? '隐藏' : '显示'} 资料来源 ({message.metadata.sources.length})
              </button>
              
              {showSources && (
                <SourceList sources={message.metadata.sources} />
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
};