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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import { useChatStore } from '../store/chatStore'; import { MessageSquare, Trash2, SquarePen } from 'lucide-react'; import { EmptyConversations, ConversationListSkeleton } from './ui'; export function ConversationList() { const { conversations, currentConversationId, messages, agents, currentAgent, newConversation, switchConversation, deleteConversation, isLoading, } = useChatStore(); const hasActiveChat = messages.length > 0; // Show skeleton during initial load if (isLoading && conversations.length === 0 && !hasActiveChat) { return <ConversationListSkeleton count={4} />; } return ( <div className="h-full flex flex-col"> {/* Header */} <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200"> <span className="text-xs font-medium text-gray-500">对话历史</span> <button onClick={newConversation} className="p-1 text-gray-400 hover:text-orange-500 rounded" title="新对话" > <SquarePen className="w-4 h-4" /> </button> </div> <div className="flex-1 overflow-y-auto custom-scrollbar"> {/* Current active chat (unsaved) */} {hasActiveChat && !currentConversationId && ( <div className="flex items-center gap-3 px-3 py-3 bg-orange-50 border-b border-orange-100 cursor-default"> <div className="w-7 h-7 bg-orange-500 rounded-lg flex items-center justify-center text-white flex-shrink-0"> <MessageSquare className="w-3.5 h-3.5" /> </div> <div className="flex-1 min-w-0"> <div className="text-xs font-medium text-orange-700 truncate">当前对话</div> <div className="text-[11px] text-orange-500 truncate"> {messages.filter(m => m.role === 'user').length} 条消息 · {currentAgent?.name || 'ZCLAW'} </div> </div> </div> )} {/* Saved conversations */} {conversations.map((conv) => { const isActive = conv.id === currentConversationId; const msgCount = conv.messages.filter(m => m.role === 'user').length; const timeStr = formatTime(conv.updatedAt); const agentName = conv.agentId ? agents.find((agent) => agent.id === conv.agentId)?.name || conv.agentId : 'ZCLAW'; return ( <div key={conv.id} onClick={() => switchConversation(conv.id)} className={`group flex items-center gap-3 px-3 py-3 cursor-pointer border-b border-gray-50 transition-colors ${ isActive ? 'bg-orange-50' : 'hover:bg-gray-100' }`} > <div className={`w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 ${ isActive ? 'bg-orange-500 text-white' : 'bg-gray-200 text-gray-500' }`}> <MessageSquare className="w-3.5 h-3.5" /> </div> <div className="flex-1 min-w-0"> <div className={`text-xs font-medium truncate ${isActive ? 'text-orange-700' : 'text-gray-900'}`}> {conv.title} </div> <div className="text-[11px] text-gray-400 truncate"> {msgCount} 条消息 · {agentName} · {timeStr} </div> </div> <button onClick={(e) => { e.stopPropagation(); if (confirm('删除该对话?')) { deleteConversation(conv.id); } }} className="opacity-0 group-hover:opacity-100 p-1 text-gray-300 hover:text-red-500 transition-opacity" title="删除" > <Trash2 className="w-3 h-3" /> </button> </div> ); })} {conversations.length === 0 && !hasActiveChat && ( <EmptyConversations size="sm" className="h-auto" /> )} </div> </div> ); } function formatTime(date: Date): string { const now = new Date(); const d = new Date(date); const diffMs = now.getTime() - d.getTime(); const diffMin = Math.floor(diffMs / 60000); if (diffMin < 1) return '刚刚'; if (diffMin < 60) return `${diffMin} 分钟前`; const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return `${diffHr} 小时前`; const diffDay = Math.floor(diffHr / 24); if (diffDay < 7) return `${diffDay} 天前`; return `${d.getMonth() + 1}/${d.getDate()}`; } |