Files
zclaw_openfang/desktop/src/App.tsx
iven e3d164e9d2 feat(ui): enhance UI with animations, dark mode support and and improved components
- Add framer-motion page transitions and AnimatePresence support
- Add dark mode support across all components
- Create reusable UI components (Button, Badge, Card, EmptyState, Input, Toast, Skeleton)
- Add CSS custom properties for consistent theming
- Add animation variants and utility functions
- Improve ChatArea, Sidebar, TriggersPanel with animations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 17:24:40 +08:00

128 lines
4.3 KiB
TypeScript

import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import './index.css';
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 { TeamCollaborationView } from './components/TeamCollaborationView';
import { useGatewayStore } from './store/gatewayStore';
import { useTeamStore } from './store/teamStore';
import { getStoredGatewayToken } from './lib/gateway-client';
import { pageVariants, defaultTransition, fadeInVariants } from './lib/animations';
import { Bot, Users } from 'lucide-react';
import { EmptyState } from './components/ui';
type View = 'main' | 'settings';
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 { connect, connectionState } = useGatewayStore();
const { activeTeam, setActiveTeam, teams } = useTeamStore();
useEffect(() => {
document.title = 'ZCLAW';
}, []);
useEffect(() => {
if (connectionState === 'disconnected') {
const gatewayToken = getStoredGatewayToken();
connect(undefined, gatewayToken).catch(() => {});
}
}, [connect, connectionState]);
// 当切换到非 hands 视图时清除选中的 Hand
const handleMainViewChange = (view: MainViewType) => {
setMainContentView(view);
if (view !== 'hands') {
// 可选:清除选中的 Hand
// setSelectedHandId(undefined);
}
};
const handleSelectTeam = (teamId: string) => {
const team = teams.find(t => t.id === teamId);
if (team) {
setActiveTeam(team);
setSelectedTeamId(teamId);
}
};
if (view === 'settings') {
return <SettingsLayout onBack={() => setView('main')} />;
}
return (
<div className="h-screen flex overflow-hidden text-gray-800 text-sm">
{/* 左侧边栏 */}
<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."
/>
)
) : (
<ChatArea />
)}
</motion.main>
</AnimatePresence>
{/* 右侧边栏 */}
<RightPanel />
</div>
);
}
export default App;