feat: outils gestion fichiers dans le VWB (📁 Fichiers)
- 5 actions : lister, créer dossier, déplacer, copier, classer par extension - Exécution sur Windows via agent port 5006 - Sécurité chemins (bloque C:\Windows, /etc, etc.) - Propriétés panel + preview canvas pour chaque action Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1155,6 +1155,140 @@ export default function PropertiesPanel({ step, onUpdateParams, onDelete }: Prop
|
||||
</>
|
||||
);
|
||||
|
||||
// === GESTION DE FICHIERS ===
|
||||
case 'file_list_dir':
|
||||
return (
|
||||
<>
|
||||
<div className="prop-section-title">
|
||||
<span className="icon">📂</span> Lister un dossier
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin du dossier</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.path || '')}
|
||||
onChange={(e) => updateParam('path', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\anonymise"
|
||||
/>
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Filtre (glob)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.pattern || '*')}
|
||||
onChange={(e) => updateParam('pattern', e.target.value)}
|
||||
placeholder="*.pdf, *.*, *.docx"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'file_create_dir':
|
||||
return (
|
||||
<>
|
||||
<div className="prop-section-title">
|
||||
<span className="icon">📁</span> Créer un dossier
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin du dossier</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.path || '')}
|
||||
onChange={(e) => updateParam('path', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\anonymise\pdf"
|
||||
/>
|
||||
</div>
|
||||
<div className="prop-info">
|
||||
Les dossiers parents seront créés automatiquement si nécessaire.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'file_move':
|
||||
return (
|
||||
<>
|
||||
<div className="prop-section-title">
|
||||
<span className="icon">📎</span> Déplacer un fichier
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin source</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.source || '')}
|
||||
onChange={(e) => updateParam('source', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\document.pdf"
|
||||
/>
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin destination</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.destination || '')}
|
||||
onChange={(e) => updateParam('destination', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\anonymise\pdf\document.pdf"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'file_copy':
|
||||
return (
|
||||
<>
|
||||
<div className="prop-section-title">
|
||||
<span className="icon">📋</span> Copier un fichier
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin source</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.source || '')}
|
||||
onChange={(e) => updateParam('source', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\document.pdf"
|
||||
/>
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Chemin destination</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.destination || '')}
|
||||
onChange={(e) => updateParam('destination', e.target.value)}
|
||||
placeholder="C:\Users\dom\Archives\document.pdf"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'file_sort_by_ext':
|
||||
return (
|
||||
<>
|
||||
<div className="prop-section-title">
|
||||
<span className="icon">🗂️</span> Classer par extension
|
||||
</div>
|
||||
<div className="prop-field">
|
||||
<label>Dossier source</label>
|
||||
<input
|
||||
type="text"
|
||||
value={String(params.source_dir || '')}
|
||||
onChange={(e) => updateParam('source_dir', e.target.value)}
|
||||
placeholder="C:\Users\dom\Downloads\anonymise"
|
||||
/>
|
||||
</div>
|
||||
<div className="prop-field checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(params.create_subdirs !== false)}
|
||||
onChange={(e) => updateParam('create_subdirs', e.target.checked)}
|
||||
/>
|
||||
Créer les sous-dossiers automatiquement
|
||||
</label>
|
||||
</div>
|
||||
<div className="prop-info">
|
||||
Les fichiers seront déplacés dans des sous-dossiers nommés par extension (pdf/, docx/, jpg/, etc.)
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// === VALIDATION ===
|
||||
case 'verify_element_exists':
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ function StepNode({ data, selected }: StepNodeProps) {
|
||||
const isConditional = step.action_type === 'visual_condition' || step.action_type === 'loop_visual';
|
||||
const isDataLoop = step.action_type === 'db_foreach';
|
||||
const isImport = step.action_type === 'import_excel';
|
||||
const isFileAction = step.action_type.startsWith('file_');
|
||||
|
||||
// État du tooltip d'aide
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
@@ -34,7 +35,7 @@ function StepNode({ data, selected }: StepNodeProps) {
|
||||
}, [showHelp]);
|
||||
|
||||
return (
|
||||
<div className={`step-node ${selected ? 'selected' : ''} ${isConditional ? 'conditional' : ''} ${isDataLoop ? 'data-loop' : ''} ${isImport ? 'data-import' : ''}`}>
|
||||
<div className={`step-node ${selected ? 'selected' : ''} ${isConditional ? 'conditional' : ''} ${isDataLoop ? 'data-loop' : ''} ${isImport ? 'data-import' : ''} ${isFileAction ? 'file-action' : ''}`}>
|
||||
{/* Bouton aide (?) */}
|
||||
{action && (
|
||||
<button
|
||||
@@ -82,7 +83,8 @@ function StepNode({ data, selected }: StepNodeProps) {
|
||||
title="Supprimer (Suppr)"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
data.onDelete?.(step.id);
|
||||
// Dispatch un custom event que App.tsx écoute (contourne le memo)
|
||||
window.dispatchEvent(new CustomEvent('rpa-delete-step', { detail: step.id }));
|
||||
}}
|
||||
>
|
||||
×
|
||||
@@ -152,6 +154,29 @@ function StepNode({ data, selected }: StepNodeProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Aperçu actions fichiers */}
|
||||
{step.action_type === 'file_list_dir' && typeof step.parameters?.path === 'string' && step.parameters.path.length > 0 && (
|
||||
<div className="step-node-params">
|
||||
{`📂 ${String(step.parameters.path).split(/[\\/]/).pop() || String(step.parameters.path)}`}
|
||||
{step.parameters.pattern && step.parameters.pattern !== '*' ? ` (${String(step.parameters.pattern)})` : ''}
|
||||
</div>
|
||||
)}
|
||||
{step.action_type === 'file_create_dir' && typeof step.parameters?.path === 'string' && step.parameters.path.length > 0 && (
|
||||
<div className="step-node-params">
|
||||
{`📁 ${String(step.parameters.path).split(/[\\/]/).pop() || String(step.parameters.path)}`}
|
||||
</div>
|
||||
)}
|
||||
{(step.action_type === 'file_move' || step.action_type === 'file_copy') && typeof step.parameters?.source === 'string' && step.parameters.source.length > 0 && (
|
||||
<div className="step-node-params">
|
||||
{`${String(step.parameters.source).split(/[\\/]/).pop()} → ${String(step.parameters.destination || '?').split(/[\\/]/).pop()}`}
|
||||
</div>
|
||||
)}
|
||||
{step.action_type === 'file_sort_by_ext' && typeof step.parameters?.source_dir === 'string' && step.parameters.source_dir.length > 0 && (
|
||||
<div className="step-node-params">
|
||||
{`🗂️ ${String(step.parameters.source_dir).split(/[\\/]/).pop() || String(step.parameters.source_dir)}`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!step.anchor_id && action?.needsAnchor && (
|
||||
<div className="step-node-warning">
|
||||
Ancre requise
|
||||
|
||||
@@ -55,14 +55,20 @@ export type ActionType =
|
||||
| 'llm_analyze'
|
||||
| 'llm_translate'
|
||||
| 'llm_extract_data'
|
||||
| 'llm_generate';
|
||||
| 'llm_generate'
|
||||
// === Gestion de fichiers ===
|
||||
| 'file_list_dir'
|
||||
| 'file_create_dir'
|
||||
| 'file_move'
|
||||
| 'file_copy'
|
||||
| 'file_sort_by_ext';
|
||||
|
||||
export interface ActionDefinition {
|
||||
type: ActionType;
|
||||
label: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
category: 'mouse' | 'keyboard' | 'wait' | 'data' | 'logic' | 'ai' | 'llm' | 'validation';
|
||||
category: 'mouse' | 'keyboard' | 'wait' | 'data' | 'logic' | 'ai' | 'llm' | 'validation' | 'files';
|
||||
needsAnchor: boolean;
|
||||
params: { name: string; type: string; description: string }[];
|
||||
}
|
||||
@@ -200,6 +206,27 @@ export const ACTIONS: ActionDefinition[] = [
|
||||
{ name: 'model', type: 'string', description: 'Modèle Ollama' }
|
||||
] },
|
||||
|
||||
// === GESTION DE FICHIERS ===
|
||||
{ type: 'file_list_dir', label: 'Lister un dossier', icon: '📂', description: 'Liste les fichiers d\'un dossier et retourne leurs noms et extensions.', category: 'files', needsAnchor: false, params: [
|
||||
{ name: 'path', type: 'string', description: 'Chemin du dossier' },
|
||||
{ name: 'pattern', type: 'string', description: 'Filtre (ex: *.pdf, *.*)' },
|
||||
] },
|
||||
{ type: 'file_create_dir', label: 'Créer un dossier', icon: '📁', description: 'Crée un dossier (et les sous-dossiers si nécessaire).', category: 'files', needsAnchor: false, params: [
|
||||
{ name: 'path', type: 'string', description: 'Chemin du dossier à créer' },
|
||||
] },
|
||||
{ type: 'file_move', label: 'Déplacer un fichier', icon: '📎', description: 'Déplace ou renomme un fichier.', category: 'files', needsAnchor: false, params: [
|
||||
{ name: 'source', type: 'string', description: 'Chemin source' },
|
||||
{ name: 'destination', type: 'string', description: 'Chemin destination' },
|
||||
] },
|
||||
{ type: 'file_copy', label: 'Copier un fichier', icon: '📋', description: 'Copie un fichier vers un autre emplacement.', category: 'files', needsAnchor: false, params: [
|
||||
{ name: 'source', type: 'string', description: 'Chemin source' },
|
||||
{ name: 'destination', type: 'string', description: 'Chemin destination' },
|
||||
] },
|
||||
{ type: 'file_sort_by_ext', label: 'Classer par extension', icon: '🗂️', description: 'Crée des sous-dossiers par extension et déplace les fichiers.', category: 'files', needsAnchor: false, params: [
|
||||
{ name: 'source_dir', type: 'string', description: 'Dossier source' },
|
||||
{ name: 'create_subdirs', type: 'boolean', description: 'Créer les sous-dossiers automatiquement' },
|
||||
] },
|
||||
|
||||
// === VALIDATION ===
|
||||
{ type: 'verify_element_exists', label: 'Vérifier présence', icon: '✅', description: 'Vérifie qu\'un élément visuel est présent à l\'écran.', category: 'validation', needsAnchor: true, params: [
|
||||
{ name: 'timeout_ms', type: 'number', description: 'Délai max d\'attente en millisecondes' }
|
||||
@@ -217,6 +244,7 @@ export const ACTION_CATEGORIES = {
|
||||
logic: { label: 'Logique', icon: '🔀' },
|
||||
ai: { label: 'IA', icon: '🤖' },
|
||||
llm: { label: 'IA / LLM', icon: '🧪' },
|
||||
files: { label: 'Fichiers', icon: '📁' },
|
||||
validation: { label: 'Validation', icon: '✅' },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user