feat: capture Windows auto-détection OS, chat Léa agrandi, UX améliorée
- Capture auto : détecte OS navigateur → capture Windows ou Linux - Timer capture utilise aussi la smart capture - Heartbeat background permanent (même sans session) - Tri screenshots par date (plus de vieilles captures) - Chat Léa : 450x650, polices 11pt, redimensionnable, meilleur contraste - Bouton Exécuter : "Linux" + "Windows" avec feedback visuel - Délai 5s avant replay Windows (temps de réduire le navigateur) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,9 +111,30 @@ export default function CapturePanel({
|
||||
}
|
||||
};
|
||||
|
||||
// Capture intelligente — auto-détection OS
|
||||
const doSmartCapture = async () => {
|
||||
const isWindows = navigator.platform?.includes('Win') || navigator.userAgent?.includes('Windows');
|
||||
if (isWindows) {
|
||||
try {
|
||||
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (data.image) {
|
||||
setCurrentCapture({
|
||||
screenshot_base64: data.image,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
source: 'windows',
|
||||
} as any);
|
||||
}
|
||||
} catch {}
|
||||
} else {
|
||||
onCapture();
|
||||
}
|
||||
};
|
||||
|
||||
const handleTimerCapture = () => {
|
||||
if (timerSeconds === 0) {
|
||||
onCapture();
|
||||
doSmartCapture();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -127,7 +148,7 @@ export default function CapturePanel({
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
setCountdown(null);
|
||||
onCapture();
|
||||
doSmartCapture();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
@@ -145,33 +166,10 @@ export default function CapturePanel({
|
||||
<div className="capture-panel">
|
||||
<h3>Capture</h3>
|
||||
|
||||
{/* Contrôles de capture */}
|
||||
{/* Capture — auto-détection OS navigateur */}
|
||||
<div className="capture-controls">
|
||||
<button onClick={onCapture} disabled={countdown !== null}>
|
||||
Capturer
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (data.image) {
|
||||
const fakeCapture = {
|
||||
screenshot_base64: data.image,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
source: 'windows',
|
||||
};
|
||||
setCurrentCapture(fakeCapture as any);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Capture Windows échouée:', err);
|
||||
}
|
||||
}}
|
||||
title="Capture le dernier écran du PC Windows"
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
🖥️ Windows
|
||||
<button disabled={countdown !== null} onClick={doSmartCapture}>
|
||||
📸 Capturer
|
||||
</button>
|
||||
<select value={timerSeconds} onChange={(e) => setTimerSeconds(Number(e.target.value))}>
|
||||
<option value="0">Immédiat</option>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import { useState } from 'react';
|
||||
import type { Execution } from '../types';
|
||||
|
||||
interface Props {
|
||||
@@ -9,20 +10,19 @@ interface Props {
|
||||
|
||||
export default function ExecutionControls({ execution, onStart, onStop }: Props) {
|
||||
const isRunning = execution?.status === 'running' || execution?.status === 'paused';
|
||||
const [windowsStatus, setWindowsStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
|
||||
|
||||
const handleExecuteWindows = async () => {
|
||||
setWindowsStatus('sending');
|
||||
try {
|
||||
// Récupérer le workflow actif depuis l'état de la session
|
||||
const stateResp = await fetch('/api/v3/session/state');
|
||||
const state = await stateResp.json();
|
||||
let workflowId = state?.session?.active_workflow_id;
|
||||
let steps = state?.workflow?.steps || [];
|
||||
|
||||
// Si pas de workflow actif, essayer de charger le premier disponible
|
||||
if (!steps.length && state?.workflows_list?.length) {
|
||||
const firstWf = state.workflows_list[0];
|
||||
workflowId = firstWf.id;
|
||||
// Charger les étapes du workflow
|
||||
const wfResp = await fetch(`/api/v3/workflow/${firstWf.id}`);
|
||||
const wfData = await wfResp.json();
|
||||
steps = wfData?.steps || wfData?.workflow?.steps || [];
|
||||
@@ -30,10 +30,10 @@ export default function ExecutionControls({ execution, onStart, onStop }: Props)
|
||||
|
||||
if (!steps.length) {
|
||||
alert('Aucune étape dans le workflow. Sélectionnez un workflow d\'abord.');
|
||||
setWindowsStatus('idle');
|
||||
return;
|
||||
}
|
||||
|
||||
// Via le proxy Vite (/api → port 5002)
|
||||
const resp = await fetch('/api/v3/execute-windows', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -52,36 +52,50 @@ export default function ExecutionControls({ execution, onStart, onStop }: Props)
|
||||
|
||||
const result = await resp.json();
|
||||
if (result.replay_id) {
|
||||
alert(`Replay lancé sur Windows ! ID: ${result.replay_id}`);
|
||||
setWindowsStatus('sent');
|
||||
alert('Replay lancé ! Réduisez cette fenêtre maintenant.\nLes actions commenceront dans 5 secondes.');
|
||||
setTimeout(() => setWindowsStatus('idle'), 5000);
|
||||
} else {
|
||||
alert(`Erreur: ${result.error || JSON.stringify(result)}`);
|
||||
setWindowsStatus('error');
|
||||
setTimeout(() => setWindowsStatus('idle'), 3000);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Erreur connexion streaming server: ${err}`);
|
||||
alert(`Erreur connexion: ${err}`);
|
||||
setWindowsStatus('error');
|
||||
setTimeout(() => setWindowsStatus('idle'), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="execution-controls">
|
||||
{!isRunning ? (
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
<button className="btn-start" onClick={onStart}>
|
||||
▶️ Exécuter
|
||||
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
|
||||
<button className="btn-start" onClick={onStart} title="Exécuter sur cet écran (Linux)">
|
||||
🐧 Linux
|
||||
</button>
|
||||
<button
|
||||
className="btn-start"
|
||||
onClick={handleExecuteWindows}
|
||||
style={{ background: '#0078d4', fontSize: '12px' }}
|
||||
title="Envoyer les actions au PC Windows via le streaming server"
|
||||
disabled={windowsStatus === 'sending'}
|
||||
style={{
|
||||
background: windowsStatus === 'sent' ? '#22c55e' : windowsStatus === 'error' ? '#ef4444' : '#0078d4',
|
||||
fontSize: '12px',
|
||||
opacity: windowsStatus === 'sending' ? 0.6 : 1,
|
||||
}}
|
||||
title="Exécuter sur le PC Windows distant"
|
||||
>
|
||||
🖥️ Windows
|
||||
{windowsStatus === 'sending' ? '⏳ Envoi...' :
|
||||
windowsStatus === 'sent' ? '✅ Lancé !' :
|
||||
windowsStatus === 'error' ? '❌ Erreur' :
|
||||
'🖥️ Windows'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="exec-status">
|
||||
<span className={`status-badge ${execution?.status}`}>
|
||||
{execution?.status === 'running' ? 'En cours' : 'En pause'}
|
||||
{execution?.status === 'running' ? '⏳ En cours' : '⏸️ En pause'}
|
||||
</span>
|
||||
<span className="exec-progress">
|
||||
{execution?.completed_steps}/{execution?.total_steps}
|
||||
@@ -93,10 +107,6 @@ export default function ExecutionControls({ execution, onStart, onStop }: Props)
|
||||
</>
|
||||
)}
|
||||
|
||||
{execution?.status === 'completed' && (
|
||||
<span className="status-badge completed">Terminé</span>
|
||||
)}
|
||||
|
||||
{execution?.status === 'error' && (
|
||||
<span className="status-badge error" title={execution.error_message}>
|
||||
Erreur
|
||||
|
||||
Reference in New Issue
Block a user