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

@@ -0,0 +1,256 @@
/**
* SchedulerPanel - OpenFang Scheduler UI
*
* Displays scheduled jobs, event triggers, and run history.
*
* Design based on OpenFang Dashboard v0.4.0
*/
import { useState, useEffect, useCallback } from 'react';
import { useGatewayStore } from '../store/gatewayStore';
import {
Clock,
Zap,
History,
Plus,
RefreshCw,
Loader2,
Calendar,
} from 'lucide-react';
// === Tab Types ===
type TabType = 'scheduled' | 'triggers' | 'history';
// === Tab Button Component ===
function TabButton({
active,
onClick,
icon: Icon,
label,
}: {
active: boolean;
onClick: () => void;
icon: React.ComponentType<{ className?: string }>;
label: string;
}) {
return (
<button
onClick={onClick}
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors ${
active
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
<Icon className="w-3.5 h-3.5" />
{label}
</button>
);
}
// === Empty State Component ===
function EmptyState({
icon: Icon,
title,
description,
actionLabel,
onAction,
}: {
icon: React.ComponentType<{ className?: string }>;
title: string;
description: string;
actionLabel?: string;
onAction?: () => void;
}) {
return (
<div className="text-center py-8">
<div className="w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center mx-auto mb-3">
<Icon className="w-6 h-6 text-gray-400" />
</div>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">{title}</p>
<p className="text-xs text-gray-400 dark:text-gray-500 mb-4 max-w-sm mx-auto">
{description}
</p>
{actionLabel && onAction && (
<button
onClick={onAction}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus className="w-4 h-4" />
{actionLabel}
</button>
)}
</div>
);
}
// === Main SchedulerPanel Component ===
export function SchedulerPanel() {
const { scheduledTasks, loadScheduledTasks, isLoading } = useGatewayStore();
const [activeTab, setActiveTab] = useState<TabType>('scheduled');
useEffect(() => {
loadScheduledTasks();
}, [loadScheduledTasks]);
const handleCreateJob = useCallback(() => {
// TODO: Implement job creation modal
alert('定时任务创建功能即将推出!');
}, []);
const handleCreateTrigger = useCallback(() => {
// TODO: Implement trigger creation modal
alert('事件触发器创建功能即将推出!');
}, []);
if (isLoading && scheduledTasks.length === 0) {
return (
<div className="p-4 text-center">
<Loader2 className="w-6 h-6 animate-spin mx-auto text-gray-400 mb-2" />
<p className="text-sm text-gray-500 dark:text-gray-400">
...
</p>
</div>
);
}
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
</h2>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
</p>
</div>
<button
onClick={() => loadScheduledTasks()}
disabled={isLoading}
className="text-sm text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1 disabled:opacity-50"
>
{isLoading ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<RefreshCw className="w-3.5 h-3.5" />
)}
</button>
</div>
{/* Tab Navigation */}
<div className="flex items-center justify-between">
<div className="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
<TabButton
active={activeTab === 'scheduled'}
onClick={() => setActiveTab('scheduled')}
icon={Clock}
label="定时任务"
/>
<TabButton
active={activeTab === 'triggers'}
onClick={() => setActiveTab('triggers')}
icon={Zap}
label="事件触发器"
/>
<TabButton
active={activeTab === 'history'}
onClick={() => setActiveTab('history')}
icon={History}
label="运行历史"
/>
</div>
{activeTab === 'scheduled' && (
<button
onClick={handleCreateJob}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus className="w-4 h-4" />
</button>
)}
</div>
{/* Tab Content */}
{activeTab === 'scheduled' && (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
{scheduledTasks.length === 0 ? (
<EmptyState
icon={Calendar}
title="暂无定时任务"
description="创建一个定时任务来定期运行代理。"
actionLabel="创建定时任务"
onAction={handleCreateJob}
/>
) : (
<div className="space-y-2">
{scheduledTasks.map((task) => (
<div
key={task.id}
className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900 rounded-lg"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
<Clock className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</div>
<div>
<div className="font-medium text-gray-900 dark:text-white">
{task.name}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{task.schedule}
</div>
</div>
</div>
<div className="flex items-center gap-2">
<span
className={`px-2 py-0.5 rounded text-xs ${
task.status === 'active'
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
: task.status === 'paused'
? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400'
}`}
>
{task.status === 'active' ? '运行中' : task.status === 'paused' ? '已暂停' : task.status}
</span>
</div>
</div>
))}
</div>
)}
</div>
)}
{activeTab === 'triggers' && (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<EmptyState
icon={Zap}
title="暂无事件触发器"
description="事件触发器在系统事件(如收到消息、文件更改或 API webhook发生时触发代理执行。"
actionLabel="创建事件触发器"
onAction={handleCreateTrigger}
/>
</div>
)}
{activeTab === 'history' && (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<EmptyState
icon={History}
title="暂无运行历史"
description="当定时任务或事件触发器执行时,运行记录将显示在这里,包括状态和日志。"
/>
</div>
)}
</div>
);
}
export default SchedulerPanel;