首页布局优化前

This commit is contained in:
iven
2026-03-17 23:26:16 +08:00
parent 74dbf42644
commit e262200f1e
89 changed files with 2266 additions and 2120 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import './index.css';
import { Sidebar, MainViewType } from './components/Sidebar';
@@ -9,14 +9,19 @@ import { HandTaskPanel } from './components/HandTaskPanel';
import { SchedulerPanel } from './components/SchedulerPanel';
import { TeamCollaborationView } from './components/TeamCollaborationView';
import { SwarmDashboard } from './components/SwarmDashboard';
import { useGatewayStore } from './store/gatewayStore';
import { SkillMarket } from './components/SkillMarket';
import { AgentOnboardingWizard } from './components/AgentOnboardingWizard';
import { HandApprovalModal } from './components/HandApprovalModal';
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 { silentErrorHandler } from './lib/error-utils';
import { Bot, Users, Loader2 } from 'lucide-react';
import { EmptyState } from './components/ui';
import { isTauriRuntime, getLocalGatewayStatus, startLocalGateway } from './lib/tauri-gateway';
import { useOnboarding } from './lib/use-onboarding';
import type { Clone } from './store/agentStore';
type View = 'main' | 'settings';
@@ -39,13 +44,66 @@ function App() {
const [selectedTeamId, setSelectedTeamId] = useState<string | undefined>(undefined);
const [bootstrapping, setBootstrapping] = useState(true);
const [bootstrapStatus, setBootstrapStatus] = useState('Initializing...');
const { connect, connectionState } = useGatewayStore();
const [showOnboarding, setShowOnboarding] = useState(false);
// Hand Approval state
const [pendingApprovalRun, setPendingApprovalRun] = useState<HandRun | null>(null);
const [showApprovalModal, setShowApprovalModal] = useState(false);
const { connect, hands, approveHand, loadHands } = useGatewayStore();
const { activeTeam, setActiveTeam, teams } = useTeamStore();
const { setCurrentAgent } = useChatStore();
const { isNeeded: onboardingNeeded, isLoading: onboardingLoading, markCompleted } = useOnboarding();
useEffect(() => {
document.title = 'ZCLAW';
}, []);
// Watch for Hands that need approval
useEffect(() => {
const handsNeedingApproval = hands.filter(h => h.status === 'needs_approval');
if (handsNeedingApproval.length > 0 && !showApprovalModal) {
// Find the first hand with needs_approval and create a pending run
const hand = handsNeedingApproval[0];
if (hand.currentRunId) {
setPendingApprovalRun({
runId: hand.currentRunId,
status: 'needs_approval',
startedAt: new Date().toISOString(),
});
setShowApprovalModal(true);
}
}
}, [hands, showApprovalModal]);
// Handle approval/rejection of Hand runs
const handleApproveHand = useCallback(async (runId: string) => {
// Find the hand that owns this run
const hand = hands.find(h => h.currentRunId === runId);
if (!hand) return;
await approveHand(hand.id, runId, true);
await loadHands();
setShowApprovalModal(false);
setPendingApprovalRun(null);
}, [hands, approveHand, loadHands]);
const handleRejectHand = useCallback(async (runId: string, reason: string) => {
// Find the hand that owns this run
const hand = hands.find(h => h.currentRunId === runId);
if (!hand) return;
await approveHand(hand.id, runId, false, reason);
await loadHands();
setShowApprovalModal(false);
setPendingApprovalRun(null);
}, [hands, approveHand, loadHands]);
const handleCloseApprovalModal = useCallback(() => {
setShowApprovalModal(false);
// Don't clear pendingApprovalRun - keep it for reference
}, []);
// Bootstrap: Start OpenFang Gateway before rendering main UI
useEffect(() => {
let mounted = true;
@@ -64,43 +122,65 @@ function App() {
setBootstrapStatus('Starting OpenFang Gateway...');
console.log('[App] Local gateway not running, auto-starting...');
await startLocalGateway();
await startLocalGateway();
// Wait for gateway to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('[App] Local gateway started');
} else if (isRunning) {
console.log('[App] Local gateway already running');
}
} catch (err) {
console.warn('[App] Failed to check/start local gateway:', err);
// Wait for gateway to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('[App] Local gateway started');
} else if (isRunning) {
console.log('[App] Local gateway already running');
}
} catch (err) {
console.warn('[App] Failed to check/start local gateway:', err);
}
if (!mounted) return;
// Step 2: Connect to gateway
setBootstrapStatus('Connecting to gateway...');
const gatewayToken = getStoredGatewayToken();
await connect(undefined, gatewayToken);
if (!mounted) return;
// Step 3: Bootstrap complete
setBootstrapping(false);
} catch (err) {
console.error('[App] Bootstrap failed:', err);
// Still allow app to load, connection status will show error
setBootstrapping(false);
}
};
if (!mounted) return;
// Step 2: Connect to gateway
setBootstrapStatus('Connecting to gateway...');
const gatewayToken = getStoredGatewayToken();
await connect(undefined, gatewayToken);
if (!mounted) return;
// Step 3: Check if onboarding is needed
if (onboardingNeeded && !onboardingLoading) {
setShowOnboarding(true);
}
// Step 4: Bootstrap complete
setBootstrapping(false);
} catch (err) {
console.error('[App] Bootstrap failed:', err);
// Still allow app to load, connection status will show error
setBootstrapping(false);
}
};
bootstrap();
return () => {
mounted = false;
};
}, [connect]);
}, [connect, onboardingNeeded, onboardingLoading]);
// Handle onboarding completion
const handleOnboardingSuccess = (clone: Clone) => {
markCompleted({
userName: clone.userName || 'User',
userRole: clone.userRole,
});
setCurrentAgent({
id: clone.id,
name: clone.name,
icon: clone.emoji || '🦞',
color: 'bg-gradient-to-br from-orange-500 to-red-500',
lastMessage: clone.role || 'New Agent',
time: '',
});
setShowOnboarding(false);
};
// 当切换到非 hands 视图时清除选中的 Hand
const handleMainViewChange = (view: MainViewType) => {
@@ -128,6 +208,24 @@ function App() {
return <BootstrapScreen status={bootstrapStatus} />;
}
// Show onboarding wizard for first-time users
if (showOnboarding) {
return (
<AgentOnboardingWizard
isOpen={true}
onClose={() => {
// Skip onboarding and mark as completed with default values
markCompleted({
userName: 'User',
userRole: 'user',
});
setShowOnboarding(false);
}}
onSuccess={handleOnboardingSuccess}
/>
);
}
return (
<div className="h-screen flex overflow-hidden text-gray-800 text-sm">
{/* 左侧边栏 */}
@@ -190,6 +288,15 @@ function App() {
>
<SwarmDashboard />
</motion.div>
) : mainContentView === 'skills' ? (
<motion.div
variants={fadeInVariants}
initial="initial"
animate="animate"
className="flex-1 overflow-hidden"
>
<SkillMarket />
</motion.div>
) : (
<ChatArea />
)}
@@ -198,6 +305,15 @@ function App() {
{/* 右侧边栏 */}
<RightPanel />
{/* Hand Approval Modal (global) */}
<HandApprovalModal
handRun={pendingApprovalRun}
isOpen={showApprovalModal}
onApprove={handleApproveHand}
onReject={handleRejectHand}
onClose={handleCloseApprovalModal}
/>
</div>
);
}