feat(automation): complete unified automation system redesign

Phase 4 completion:
- Add ApprovalQueue component for managing pending approvals
- Add ExecutionResult component for displaying hand/workflow results
- Update Sidebar navigation to use unified AutomationPanel
- Replace separate 'hands' and 'workflow' tabs with single 'automation' tab
- Fix TypeScript type safety issues with unknown types in JSX expressions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-18 17:12:05 +08:00
parent 3a7631e035
commit 3518fc8ece
74 changed files with 4984 additions and 687 deletions

View File

@@ -5,19 +5,21 @@ import { Sidebar, MainViewType } from './components/Sidebar';
import { ChatArea } from './components/ChatArea';
import { RightPanel } from './components/RightPanel';
import { SettingsLayout } from './components/Settings/SettingsLayout';
import { HandTaskPanel } from './components/HandTaskPanel';
import { SchedulerPanel } from './components/SchedulerPanel';
import { AutomationPanel } from './components/Automation';
import { TeamCollaborationView } from './components/TeamCollaborationView';
import { TeamOrchestrator } from './components/TeamOrchestrator';
import { SwarmDashboard } from './components/SwarmDashboard';
import { SkillMarket } from './components/SkillMarket';
import { AgentOnboardingWizard } from './components/AgentOnboardingWizard';
import { HandApprovalModal } from './components/HandApprovalModal';
import { TopBar } from './components/TopBar';
import { DetailDrawer } from './components/DetailDrawer';
import { useGatewayStore, type HandRun } from './store/gatewayStore';
import { useTeamStore } from './store/teamStore';
import { useChatStore } from './store/chatStore';
import { getStoredGatewayToken } from './lib/gateway-client';
import { pageVariants, defaultTransition, fadeInVariants } from './lib/animations';
import { Bot, Users, Loader2 } from 'lucide-react';
import { Users, Loader2, Settings } from 'lucide-react';
import { EmptyState } from './components/ui';
import { isTauriRuntime, getLocalGatewayStatus, startLocalGateway } from './lib/tauri-gateway';
import { useOnboarding } from './lib/use-onboarding';
@@ -40,15 +42,16 @@ function BootstrapScreen({ status }: { status: string }) {
function App() {
const [view, setView] = useState<View>('main');
const [mainContentView, setMainContentView] = useState<MainViewType>('chat');
const [selectedHandId, setSelectedHandId] = useState<string | undefined>(undefined);
const [selectedTeamId, setSelectedTeamId] = useState<string | undefined>(undefined);
const [bootstrapping, setBootstrapping] = useState(true);
const [bootstrapStatus, setBootstrapStatus] = useState('Initializing...');
const [showOnboarding, setShowOnboarding] = useState(false);
const [showDetailDrawer, setShowDetailDrawer] = useState(false);
// Hand Approval state
const [pendingApprovalRun, setPendingApprovalRun] = useState<HandRun | null>(null);
const [showApprovalModal, setShowApprovalModal] = useState(false);
const [teamViewMode, setTeamViewMode] = useState<'collaboration' | 'orchestrator'>('collaboration');
const { connect, hands, approveHand, loadHands } = useGatewayStore();
const { activeTeam, setActiveTeam, teams } = useTeamStore();
@@ -182,13 +185,9 @@ function App() {
setShowOnboarding(false);
};
// 当切换到非 hands 视图时清除选中的 Hand
// 处理主视图切换
const handleMainViewChange = (view: MainViewType) => {
setMainContentView(view);
if (view !== 'hands') {
// 可选:清除选中的 Hand
// setSelectedHandId(undefined);
}
};
const handleSelectTeam = (teamId: string) => {
@@ -227,84 +226,120 @@ function App() {
}
return (
<div className="h-screen flex overflow-hidden text-gray-800 text-sm">
<div className="h-screen flex overflow-hidden text-gray-800 text-sm bg-white dark:bg-gray-950">
{/* 左侧边栏 */}
<Sidebar
onOpenSettings={() => setView('settings')}
onMainViewChange={handleMainViewChange}
selectedHandId={selectedHandId}
onSelectHand={setSelectedHandId}
selectedTeamId={selectedTeamId}
onSelectTeam={handleSelectTeam}
/>
{/* 中间区域 */}
<AnimatePresence mode="wait">
<motion.main
key={mainContentView}
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
transition={defaultTransition}
className="flex-1 flex flex-col bg-white relative overflow-hidden"
>
{mainContentView === 'hands' && selectedHandId ? (
<HandTaskPanel
handId={selectedHandId}
onBack={() => setSelectedHandId(undefined)}
/>
) : mainContentView === 'hands' ? (
<EmptyState
icon={<Bot className="w-8 h-8" />}
title="Select a Hand"
description="Choose an autonomous capability package from the list on the left to view its task list and execution results."
/>
) : mainContentView === 'workflow' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="flex-1 overflow-y-auto"
>
<SchedulerPanel />
</motion.div>
) : mainContentView === 'team' ? (
activeTeam ? (
<TeamCollaborationView teamId={activeTeam.id} />
) : (
<EmptyState
icon={<Users className="w-8 h-8" />}
title="Select or Create a Team"
description="Choose a team from the list on the left, or click + to create a new multi-Agent collaboration team."
/>
)
) : mainContentView === 'swarm' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="flex-1 overflow-hidden"
>
<SwarmDashboard />
</motion.div>
) : mainContentView === 'skills' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="flex-1 overflow-hidden"
>
<SkillMarket />
</motion.div>
) : (
<ChatArea />
)}
</motion.main>
</AnimatePresence>
{/* 主内容区 */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* 顶部工具栏 */}
<TopBar
title="ZCLAW"
onOpenDetail={() => setShowDetailDrawer(true)}
/>
{/* 右侧边栏 */}
<RightPanel />
{/* 内容区域 */}
<AnimatePresence mode="wait">
<motion.main
key={mainContentView}
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
transition={defaultTransition}
className="flex-1 overflow-hidden relative"
>
{mainContentView === 'automation' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="h-full overflow-y-auto"
>
<AutomationPanel />
</motion.div>
) : mainContentView === 'team' ? (
activeTeam ? (
<div className="h-full flex flex-col">
{/* Team View Tabs */}
<div className="flex border-b border-gray-200 dark:border-gray-700 px-4">
<button
onClick={() => setTeamViewMode('collaboration')}
className={`flex items-center gap-1.5 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
teamViewMode === 'collaboration'
? 'text-orange-600 dark:text-orange-400 border-orange-500'
: 'text-gray-500 dark:text-gray-400 border-transparent hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
<Users className="w-4 h-4" />
</button>
<button
onClick={() => setTeamViewMode('orchestrator')}
className={`flex items-center gap-1.5 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${
teamViewMode === 'orchestrator'
? 'text-orange-600 dark:text-orange-400 border-orange-500'
: 'text-gray-500 dark:text-gray-400 border-transparent hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
<Settings className="w-4 h-4" />
</button>
</div>
{/* Tab Content */}
<div className="flex-1 overflow-hidden">
{teamViewMode === 'orchestrator' ? (
<TeamOrchestrator isOpen={true} onClose={() => setTeamViewMode('collaboration')} />
) : (
<TeamCollaborationView teamId={activeTeam.id} />
)}
</div>
</div>
) : (
<EmptyState
icon={<Users className="w-8 h-8" />}
title="选择或创建团队"
description="从左侧列表中选择一个团队,或点击 + 创建新的多 Agent 协作团队。"
/>
)
) : mainContentView === 'swarm' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="h-full overflow-hidden"
>
<SwarmDashboard />
</motion.div>
) : mainContentView === 'skills' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="h-full overflow-hidden"
>
<SkillMarket />
</motion.div>
) : (
<ChatArea />
)}
</motion.main>
</AnimatePresence>
</div>
{/* 详情抽屉 - 按需显示 */}
<DetailDrawer
open={showDetailDrawer}
onClose={() => setShowDetailDrawer(false)}
title="详情"
>
<RightPanel />
</DetailDrawer>
{/* Hand Approval Modal (global) */}
<HandApprovalModal