refactor(startup): simplify stack to Tauri-managed OpenFang + optional ChromeDriver
- Remove OpenFang CLI dependency from startup scripts - OpenFang now bundled with Tauri and managed via gateway_start/gateway_status commands - Add bootstrap screen in App.tsx to auto-start local gateway before UI loads - Update Makefile: replace start-no-gateway with start-desktop-only - Fix gateway config endpoints: use /api/config instead of /api/config/quick - Add Playwright dependencies for future E2E testing
This commit is contained in:
@@ -14,16 +14,31 @@ import { useTeamStore } from './store/teamStore';
|
||||
import { getStoredGatewayToken } from './lib/gateway-client';
|
||||
import { pageVariants, defaultTransition, fadeInVariants } from './lib/animations';
|
||||
import { silentErrorHandler } from './lib/error-utils';
|
||||
import { Bot, Users } from 'lucide-react';
|
||||
import { Bot, Users, Loader2 } from 'lucide-react';
|
||||
import { EmptyState } from './components/ui';
|
||||
import { isTauriRuntime, getLocalGatewayStatus, startLocalGateway } from './lib/tauri-gateway';
|
||||
|
||||
type View = 'main' | 'settings';
|
||||
|
||||
// Bootstrap component that ensures OpenFang is running before rendering main UI
|
||||
function BootstrapScreen({ status }: { status: string }) {
|
||||
return (
|
||||
<div className="h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-blue-500" />
|
||||
<p className="text-gray-600 text-sm">{status}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 { connect, connectionState } = useGatewayStore();
|
||||
const { activeTeam, setActiveTeam, teams } = useTeamStore();
|
||||
|
||||
@@ -31,12 +46,61 @@ function App() {
|
||||
document.title = 'ZCLAW';
|
||||
}, []);
|
||||
|
||||
// Bootstrap: Start OpenFang Gateway before rendering main UI
|
||||
useEffect(() => {
|
||||
if (connectionState === 'disconnected') {
|
||||
const gatewayToken = getStoredGatewayToken();
|
||||
connect(undefined, gatewayToken).catch(silentErrorHandler('App'));
|
||||
}
|
||||
}, [connect, connectionState]);
|
||||
let mounted = true;
|
||||
|
||||
const bootstrap = async () => {
|
||||
try {
|
||||
// Step 1: Check and start local gateway in Tauri environment
|
||||
if (isTauriRuntime()) {
|
||||
setBootstrapStatus('Checking gateway status...');
|
||||
|
||||
try {
|
||||
const status = await getLocalGatewayStatus();
|
||||
const isRunning = status.portStatus === 'busy' || status.listenerPids.length > 0;
|
||||
|
||||
if (!isRunning && status.cliAvailable) {
|
||||
setBootstrapStatus('Starting OpenFang Gateway...');
|
||||
console.log('[App] Local gateway not running, auto-starting...');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
bootstrap();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
// 当切换到非 hands 视图时清除选中的 Hand
|
||||
const handleMainViewChange = (view: MainViewType) => {
|
||||
@@ -59,6 +123,11 @@ function App() {
|
||||
return <SettingsLayout onBack={() => setView('main')} />;
|
||||
}
|
||||
|
||||
// Show bootstrap screen while starting gateway
|
||||
if (bootstrapping) {
|
||||
return <BootstrapScreen status={bootstrapStatus} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden text-gray-800 text-sm">
|
||||
{/* 左侧边栏 */}
|
||||
|
||||
Reference in New Issue
Block a user