File size: 7,266 Bytes
204e35b
ddac6a2
e1fb264
204e35b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddac6a2
204e35b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1fb264
204e35b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddac6a2
204e35b
 
 
 
 
 
 
 
 
ddac6a2
 
 
 
f2fe7c8
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import React, { useState } from 'react';

function ChatList({ chats, currentChat, onSelectChat, onDeleteChat, onCreateNewChat, collapsed, isStreamingChat }) {
  const [hoveredChat, setHoveredChat] = useState(null);
  
  const formatDate = (timestamp) => {
    const date = new Date(timestamp);
    const now = new Date();
    const diffTime = Math.abs(now - date);
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    
    if (diffDays === 1) return 'Today';
    if (diffDays === 2) return 'Yesterday';
    if (diffDays <= 7) return `${diffDays} days ago`;
    return date.toLocaleDateString();
  };
  
  const truncateTitle = (title, maxLength = 25) => {
    if (title.length <= maxLength) return title;
    return title.substring(0, maxLength) + '...';
  };

  return (
    <div className="flex flex-col h-full bg-background-elevated">
      {/* New Chat Button */}
      <div className="p-3 border-b border-border">
        <button 
          className="w-full flex items-center justify-center gap-2 py-3 px-4 bg-primary hover:bg-primary-hover text-white border-none rounded-xl cursor-pointer text-sm font-medium transition-all duration-fast shadow-sm hover:shadow-md active:scale-[0.98]"
          onClick={onCreateNewChat}
        >
          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
          </svg>
          <span>New Conversation</span>
        </button>
      </div>
      
      {/* Chat List */}
      <div className="flex-1 overflow-y-auto p-2">
        {chats.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-12 px-4 text-center">
            <div className="w-16 h-16 bg-background-secondary rounded-full flex items-center justify-center mb-4">
              <svg className="w-8 h-8 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
              </svg>
            </div>
            <h3 className="text-sm font-medium text-secondary mb-2">No conversations yet</h3>
            <p className="text-xs text-muted">Start a new conversation to get started</p>
          </div>
        ) : (
          <div className="space-y-1">
            {chats.map(chat => {
              const isActive = chat.id === currentChat?.id;
              const isStreaming = isStreamingChat && typeof isStreamingChat === 'function' && isStreamingChat(chat.id);
              const isHovered = hoveredChat === chat.id;
              
              return (
                <div
                  key={chat.id}
                  className={`group relative flex items-center p-3 rounded-lg cursor-pointer transition-all duration-fast ${
                    isActive 
                      ? 'bg-primary-light border border-primary/20 shadow-sm' 
                      : 'hover:bg-background-secondary border border-transparent'
                  }`}
                  onClick={() => onSelectChat(chat.id)}
                  onMouseEnter={() => setHoveredChat(chat.id)}
                  onMouseLeave={() => setHoveredChat(null)}
                >
                  {/* Streaming indicator */}
                  {isStreaming && (
                    <div className="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-info to-primary rounded-r-full animate-pulse" />
                  )}
                  
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2 mb-1">
                      {/* Chat icon */}
                      <div className={`flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center ${
                        isActive ? 'bg-primary text-white' : 'bg-background-tertiary text-muted'
                      }`}>
                        {isStreaming ? (
                          <div className="w-2 h-2 bg-current rounded-full animate-pulse" />
                        ) : (
                          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
                          </svg>
                        )}
                      </div>
                      
                      {/* Chat title */}
                      <div className="flex-1 min-w-0">
                        <h4 className={`text-sm font-medium truncate ${
                          isActive ? 'text-primary' : 'text-secondary'
                        }`}>
                          {truncateTitle(chat.title)}
                        </h4>
                        
                        {/* Message count and date */}
                        <div className="flex items-center justify-between mt-1">
                          <span className="text-xs text-muted">
                            {chat.messages?.length || 0} messages
                          </span>
                          {chat.messages?.length > 0 && (
                            <span className="text-xs text-muted">
                              {formatDate(chat.messages[chat.messages.length - 1]?.timestamp || Date.now())}
                            </span>
                          )}
                        </div>
                      </div>
                    </div>
                  </div>
                  
                  {/* Delete button */}
                  <button
                    className={`flex-shrink-0 ml-2 p-1.5 rounded-md transition-all duration-fast ${
                      isHovered || isActive 
                        ? 'opacity-100 hover:bg-error-light hover:text-error' 
                        : 'opacity-0 group-hover:opacity-100'
                    }`}
                    onClick={(e) => {
                      e.stopPropagation();
                      if (window.confirm('Are you sure you want to delete this conversation?')) {
                        onDeleteChat(chat.id);
                      }
                    }}
                    title="Delete conversation"
                  >
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                    </svg>
                  </button>
                </div>
              );
            })}
          </div>
        )}
      </div>
      
      {/* Footer */}
      <div className="p-3 border-t border-border">
        <div className="text-xs text-muted text-center">
          {chats.length} conversation{chats.length !== 1 ? 's' : ''}
        </div>
      </div>
    </div>
  );
}

export default ChatList;