首页布局优化前
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user