Files
gridpilot.gg/apps/companion/renderer/App.tsx

284 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useCallback } from 'react';
import { SessionCreationForm } from './components/SessionCreationForm';
import { SessionProgressMonitor } from './components/SessionProgressMonitor';
import type { HostedSessionConfig } from '../../../packages/domain/entities/HostedSessionConfig';
interface SessionProgress {
sessionId: string;
currentStep: number;
state: string;
completedSteps: number[];
hasError: boolean;
errorMessage: string | null;
}
interface PermissionStatus {
accessibility: boolean;
screenRecording: boolean;
platform: string;
}
export function App() {
const [sessionId, setSessionId] = useState<string | null>(null);
const [progress, setProgress] = useState<SessionProgress | null>(null);
const [isRunning, setIsRunning] = useState(false);
const [permissionStatus, setPermissionStatus] = useState<PermissionStatus | null>(null);
const [permissionChecking, setPermissionChecking] = useState(true);
const [missingPermissions, setMissingPermissions] = useState<string[]>([]);
const checkPermissions = useCallback(async () => {
if (!window.electronAPI) return;
setPermissionChecking(true);
try {
const result = await window.electronAPI.checkPermissions();
setPermissionStatus(result.status);
setMissingPermissions(result.missingPermissions);
} catch (error) {
console.error('Failed to check permissions:', error);
} finally {
setPermissionChecking(false);
}
}, []);
useEffect(() => {
// Check permissions on app start
checkPermissions();
if (window.electronAPI) {
window.electronAPI.onSessionProgress((newProgress: SessionProgress) => {
setProgress(newProgress);
if (newProgress.state === 'COMPLETED' ||
newProgress.state === 'FAILED' ||
newProgress.state === 'STOPPED_AT_STEP_18') {
setIsRunning(false);
}
});
}
}, [checkPermissions]);
const handleOpenPermissionSettings = async (pane?: 'accessibility' | 'screenRecording') => {
if (!window.electronAPI) return;
await window.electronAPI.openPermissionSettings(pane);
};
const handleRequestAccessibility = async () => {
if (!window.electronAPI) return;
await window.electronAPI.requestAccessibility();
// Recheck permissions after request
setTimeout(checkPermissions, 500);
};
const handleStartAutomation = async (config: HostedSessionConfig) => {
// Recheck permissions before starting
await checkPermissions();
if (missingPermissions.length > 0) {
alert(`Cannot start automation: Missing permissions: ${missingPermissions.join(', ')}`);
return;
}
setIsRunning(true);
const result = await window.electronAPI.startAutomation(config);
if (result.success && result.sessionId) {
setSessionId(result.sessionId);
} else {
setIsRunning(false);
if (result.permissionError) {
// Update permission status
await checkPermissions();
alert(`Permission Error: ${result.error}`);
} else {
alert(`Failed to start automation: ${result.error}`);
}
}
};
const handleStopAutomation = async () => {
if (sessionId) {
const result = await window.electronAPI.stopAutomation(sessionId);
if (result.success) {
setIsRunning(false);
setProgress(prev => prev ? { ...prev, state: 'STOPPED', hasError: false, errorMessage: 'User stopped automation' } : null);
} else {
alert(`Failed to stop automation: ${result.error}`);
}
}
};
const isMacOS = permissionStatus?.platform === 'darwin';
const hasAllPermissions = missingPermissions.length === 0;
return (
<div style={{
display: 'flex',
minHeight: '100vh',
backgroundColor: '#1a1a1a'
}}>
<div style={{
flex: 1,
padding: '2rem',
borderRight: '1px solid #333'
}}>
<h1 style={{ marginBottom: '2rem', color: '#fff' }}>
GridPilot Companion
</h1>
<p style={{ marginBottom: '2rem', color: '#aaa' }}>
Hosted Session Automation POC
</p>
{/* Permission Banner */}
{isMacOS && !permissionChecking && !hasAllPermissions && (
<div style={{
marginBottom: '1.5rem',
padding: '1rem',
backgroundColor: '#3d2020',
border: '1px solid #dc3545',
borderRadius: '8px',
}}>
<h3 style={{ color: '#ff6b6b', margin: '0 0 0.5rem 0', fontSize: '1rem' }}>
Missing Permissions
</h3>
<p style={{ color: '#ffaaaa', margin: '0 0 1rem 0', fontSize: '0.9rem' }}>
GridPilot requires macOS permissions to control your computer for automation.
Please grant the following permissions:
</p>
<ul style={{ color: '#ffaaaa', margin: '0 0 1rem 0', paddingLeft: '1.5rem', fontSize: '0.9rem' }}>
{!permissionStatus?.accessibility && (
<li style={{ marginBottom: '0.5rem' }}>
<strong>Accessibility:</strong> Required for keyboard and mouse control
</li>
)}
{!permissionStatus?.screenRecording && (
<li style={{ marginBottom: '0.5rem' }}>
<strong>Screen Recording:</strong> Required for screen capture and window detection
</li>
)}
</ul>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{!permissionStatus?.accessibility && (
<button
onClick={handleRequestAccessibility}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Request Accessibility
</button>
)}
{!permissionStatus?.accessibility && (
<button
onClick={() => handleOpenPermissionSettings('accessibility')}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#6c757d',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Open Accessibility Settings
</button>
)}
{!permissionStatus?.screenRecording && (
<button
onClick={() => handleOpenPermissionSettings('screenRecording')}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#6c757d',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Open Screen Recording Settings
</button>
)}
<button
onClick={checkPermissions}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#28a745',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
}}
>
Recheck Permissions
</button>
</div>
<p style={{ color: '#888', margin: '1rem 0 0 0', fontSize: '0.8rem' }}>
After granting permissions in System Preferences, click "Recheck Permissions" or restart the app.
</p>
</div>
)}
{/* Permission Status Indicator */}
{isMacOS && !permissionChecking && hasAllPermissions && (
<div style={{
marginBottom: '1.5rem',
padding: '0.75rem 1rem',
backgroundColor: '#1e3d1e',
border: '1px solid #28a745',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
}}>
<span style={{ color: '#28a745', fontSize: '1.2rem' }}></span>
<span style={{ color: '#8eff8e', fontSize: '0.9rem' }}>
All permissions granted - Ready for automation
</span>
</div>
)}
<SessionCreationForm
onSubmit={handleStartAutomation}
disabled={isRunning || (isMacOS && !hasAllPermissions)}
/>
{isRunning && (
<button
onClick={handleStopAutomation}
style={{
marginTop: '1rem',
padding: '0.75rem 1.5rem',
backgroundColor: '#dc3545',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '1rem',
fontWeight: 'bold'
}}
>
Stop Automation
</button>
)}
</div>
<div style={{
flex: 1,
padding: '2rem',
backgroundColor: '#0d0d0d'
}}>
<SessionProgressMonitor
sessionId={sessionId}
progress={progress}
isRunning={isRunning}
/>
</div>
</div>
);
}