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:
iven
2026-03-17 14:08:03 +08:00
parent 6c6d21400c
commit 74dbf42644
81 changed files with 5729 additions and 128 deletions

View File

@@ -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">
{/* 左侧边栏 */}