feat(hands): restructure Hands UI with Chinese localization

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>
This commit is contained in:
iven
2026-03-14 23:16:32 +08:00
parent 67e1da635d
commit 07079293f4
126 changed files with 36229 additions and 1035 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useChatStore, Message } from '../store/chatStore';
import { useGatewayStore } from '../store/gatewayStore';
import { Send, Paperclip, ChevronDown, Terminal, Loader2, SquarePen } from 'lucide-react';
import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp } from 'lucide-react';
const MODELS = ['glm-5', 'qwen3.5-plus', 'kimi-k2.5', 'minimax-m2.5'];
@@ -41,7 +41,7 @@ export function ChatArea() {
}, [messages]);
const handleSend = () => {
if (!input.trim() || isStreaming) return;
if (!input.trim() || isStreaming || !connected) return;
sendToGateway(input);
setInput('');
};
@@ -58,13 +58,20 @@ export function ChatArea() {
return (
<>
{/* Header */}
<div className="h-14 border-b border-gray-100 flex items-center justify-between px-6 flex-shrink-0">
<div className="h-14 border-b border-gray-100 flex items-center justify-between px-6 flex-shrink-0 bg-white">
<div className="flex items-center gap-2">
<h2 className="font-semibold text-gray-900">{currentAgent?.name || 'ZCLAW'}</h2>
<span className={`text-xs flex items-center gap-1 ${connected ? 'text-green-500' : 'text-gray-400'}`}>
<span className={`w-1.5 h-1.5 rounded-full ${connected ? 'bg-green-400' : 'bg-gray-300'}`}></span>
{connected ? 'Gateway 已连接' : 'Gateway 未连接'}
</span>
{isStreaming ? (
<span className="text-xs text-gray-400 flex items-center gap-1">
<span className="w-1.5 h-1.5 bg-gray-400 rounded-full thinking-dot"></span>
</span>
) : (
<span className={`text-xs flex items-center gap-1 ${connected ? 'text-green-500' : 'text-gray-400'}`}>
<span className={`w-1.5 h-1.5 rounded-full ${connected ? 'bg-green-400' : 'bg-gray-300'}`}></span>
{connected ? 'Gateway 已连接' : 'Gateway 未连接'}
</span>
)}
</div>
<div className="flex items-center gap-2">
{messages.length > 0 && (
@@ -81,7 +88,7 @@ export function ChatArea() {
</div>
{/* Messages */}
<div ref={scrollRef} className="flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4">
<div ref={scrollRef} className="flex-1 overflow-y-auto custom-scrollbar p-6 space-y-6">
{messages.length === 0 && (
<div className="text-center text-gray-400 py-20">
<p className="text-lg mb-2">使 ZCLAW 🦞</p>
@@ -92,13 +99,6 @@ export function ChatArea() {
{messages.map((message) => (
<MessageBubble key={message.id} message={message} />
))}
{isStreaming && (
<div className="flex items-center gap-2 text-gray-400 text-xs pl-12">
<Loader2 className="w-3 h-3 animate-spin" />
Agent ...
</div>
)}
</div>
{/* Input */}
@@ -114,10 +114,16 @@ export function ChatArea() {
value={input}
onChange={(e) => { setInput(e.target.value); adjustTextarea(); }}
onKeyDown={handleKeyDown}
placeholder={isStreaming ? 'Agent 正在回复...' : '发送给 ZCLAWShift+Enter 换行)'}
disabled={isStreaming}
placeholder={
!connected
? '请先连接 Gateway'
: isStreaming
? 'Agent 正在回复...'
: `发送给 ${currentAgent?.name || 'ZCLAW'}`
}
disabled={isStreaming || !connected}
rows={1}
className="w-full bg-transparent border-none focus:outline-none text-gray-700 placeholder-gray-400 disabled:opacity-50 resize-none leading-relaxed"
className="w-full bg-transparent border-none focus:outline-none text-gray-700 placeholder-gray-400 disabled:opacity-50 resize-none leading-relaxed mt-1"
style={{ minHeight: '24px', maxHeight: '160px' }}
/>
</div>
@@ -144,10 +150,10 @@ export function ChatArea() {
)}
<button
onClick={handleSend}
disabled={isStreaming || !input.trim()}
disabled={isStreaming || !input.trim() || !connected}
className="w-8 h-8 bg-gray-900 text-white rounded-full flex items-center justify-center hover:bg-gray-800 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<Send className="w-4 h-4" />
<ArrowUp className="w-4 h-4" />
</button>
</div>
</div>