Major changes: - Add HandList.tsx component for left sidebar - Add HandTaskPanel.tsx for middle content area - Restructure Sidebar tabs: 分身/HANDS/Workflow - Remove Hands tab from RightPanel - Localize all UI text to Chinese - Archive legacy OpenClaw documentation - Add Hands integration lessons document - Update feature checklist with new components UI improvements: - Left sidebar now shows Hands list with status icons - Middle area shows selected Hand's tasks and results - Consistent styling with Tailwind CSS - Chinese status labels and buttons Documentation: - Create docs/archive/openclaw-legacy/ for old docs - Add docs/knowledge-base/hands-integration-lessons.md - Update docs/knowledge-base/feature-checklist.md - Update docs/knowledge-base/README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
import { useChatStore } from '../store/chatStore';
|
|
import { MessageSquare, Trash2, SquarePen } from 'lucide-react';
|
|
|
|
export function ConversationList() {
|
|
const {
|
|
conversations, currentConversationId, messages, agents, currentAgent,
|
|
newConversation, switchConversation, deleteConversation,
|
|
} = useChatStore();
|
|
|
|
const hasActiveChat = messages.length > 0;
|
|
|
|
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 && (
|
|
<div className="text-center py-8 text-xs text-gray-400">
|
|
<MessageSquare className="w-8 h-8 mx-auto mb-2 opacity-30" />
|
|
<p>暂无对话</p>
|
|
<p className="mt-1">发送消息开始对话</p>
|
|
</div>
|
|
)}
|
|
</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()}`;
|
|
}
|