(null);
+
+ const allRequiredOK = useMemo(() => {
+ return safetyChecks
+ .filter((c) => c.required)
+ .every((c) => checked[c.id] === true);
+ }, [safetyChecks, checked]);
+
+ const toggle = (id: string) => {
+ setChecked((prev) => ({ ...prev, [id]: !prev[id] }));
+ };
+
+ const handleResume = async () => {
+ setSubmitting(true);
+ setError(null);
+ try {
+ const acknowledgedIds = Object.entries(checked)
+ .filter(([, v]) => v)
+ .map(([k]) => k);
+ await onResume(acknowledgedIds);
+ } catch (e: any) {
+ setError(e?.message || 'Erreur lors de la reprise');
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ // Backward compat : pas de checks -> bulle simple legacy
+ if (safetyChecks.length === 0) {
+ return (
+
+
{pauseMessage}
+ {pauseReason &&
Raison : {pauseReason}}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
Pause supervisée
+
{pauseMessage}
+ {pauseReason && (
+
+ Raison : {pauseReason}
+
+ )}
+
+
+
+ {error &&
{error}
}
+
+
+
+
+
+
+ );
+}
diff --git a/visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx b/visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx
index 2711f1a94..76efa92c1 100644
--- a/visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx
+++ b/visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx
@@ -1353,6 +1353,136 @@ export default function PropertiesPanel({ step, onUpdateParams, onDelete }: Prop
>
);
+ case 'pause_for_human': {
+ // QW4 — éditeur safety_level + safety_checks (déclaratifs)
+ const safetyChecks = Array.isArray(params.safety_checks)
+ ? (params.safety_checks as Array<{ id?: string; label?: string; required?: boolean }>)
+ : [];
+ return (
+ <>
+
+
+
+
+ {/* QW4 — Niveau de sécurité */}
+
+
+
+
+
+ {/* QW4 — Liste éditable de checks déclaratifs */}
+
+
+ {safetyChecks.map((check, i) => (
+
+ {
+ const next = [...safetyChecks];
+ next[i] = { ...check, id: e.target.value };
+ updateParam('safety_checks', next);
+ }}
+ />
+ {
+ const next = [...safetyChecks];
+ next[i] = { ...check, label: e.target.value };
+ updateParam('safety_checks', next);
+ }}
+ />
+
+
+
+ ))}
+
+
+ >
+ );
+ }
+
+ case 't2a_decision':
+ return (
+ <>
+
+
+
+
+
+ updateParam('output_var', e.target.value)}
+ placeholder="dec"
+ />
+
+
+
+ updateParam('model', e.target.value)}
+ placeholder="qwen2.5:7b"
+ />
+
+ >
+ );
+
default:
return Pas de paramètres supplémentaires
;
}
diff --git a/visual_workflow_builder/frontend_v4/src/styles.css b/visual_workflow_builder/frontend_v4/src/styles.css
index 8e77e3d89..377733528 100644
--- a/visual_workflow_builder/frontend_v4/src/styles.css
+++ b/visual_workflow_builder/frontend_v4/src/styles.css
@@ -4491,3 +4491,86 @@ body {
.right-panel-tabbed .capture-library {
border-top: 1px solid var(--border);
}
+
+/* === QW4 — PauseDialog & ChecklistPanel === */
+.pause-dialog-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+}
+.pause-dialog-simple,
+.pause-dialog-checks {
+ padding: 16px;
+ max-width: 480px;
+ background: #fff;
+ border: 2px solid #f59e0b;
+ border-radius: 8px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
+}
+.pause-dialog-checks h3 { margin: 0 0 8px; color: #92400e; }
+.pause-message { margin: 0 0 12px; }
+.pause-reason-banner {
+ background: #fef3c7;
+ padding: 8px;
+ margin-bottom: 12px;
+ border-radius: 4px;
+}
+.pause-reason { color: #6b7280; display: block; margin-top: 4px; }
+.checklist-panel {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 12px;
+}
+.check-item {
+ padding: 6px 0;
+ border-bottom: 1px solid #f3f4f6;
+}
+.check-item.required { background: #fef9c3; }
+.check-item label {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.badge {
+ font-size: 10px;
+ padding: 2px 6px;
+ border-radius: 10px;
+ margin-left: 6px;
+}
+.badge-required { background: #dc2626; color: #fff; }
+.badge-lea { background: #2563eb; color: #fff; cursor: help; }
+.check-evidence {
+ display: block;
+ font-style: italic;
+ color: #6b7280;
+ margin-left: 24px;
+}
+.pause-error {
+ color: #dc2626;
+ padding: 8px;
+ background: #fef2f2;
+ border-radius: 4px;
+ margin-bottom: 8px;
+}
+.pause-actions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+.pause-actions button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* QW4 — éditeur de safety_checks dans PropertiesPanel */
+.check-editor-row {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 4px;
+ align-items: center;
+}
diff --git a/visual_workflow_builder/frontend_v4/src/types.ts b/visual_workflow_builder/frontend_v4/src/types.ts
index 9d34e8cab..77225058f 100644
--- a/visual_workflow_builder/frontend_v4/src/types.ts
+++ b/visual_workflow_builder/frontend_v4/src/types.ts
@@ -1,5 +1,16 @@
// Types pour l'API v3
+// === QW4 — Safety checks (pause supervisée) ===
+export type SafetyLevel = 'standard' | 'medical_critical';
+
+export interface SafetyCheck {
+ id: string;
+ label: string;
+ required: boolean;
+ source: 'declarative' | 'llm_contextual';
+ evidence?: string | null;
+}
+
// Mode d'exécution
export type ExecutionMode = 'basic' | 'intelligent' | 'debug' | 'verified';
@@ -133,7 +144,9 @@ export const ACTIONS: ActionDefinition[] = [
{ name: 'max_iterations', type: 'number', description: 'Nombre maximum d\'itérations' }
] },
{ type: 'pause_for_human', label: 'Pause supervisée', icon: '⏸', description: 'Léa s\'arrête et demande validation humaine via une bulle interactive (boutons Continuer / Annuler).', category: 'logic', needsAnchor: false, params: [
- { name: 'message', type: 'string', description: 'Message affiché dans la bulle (ex: "Je ne suis pas sûre du critère 3, validez-vous UHCD ?")' }
+ { name: 'message', type: 'string', description: 'Message affiché dans la bulle (ex: "Je ne suis pas sûre du critère 3, validez-vous UHCD ?")' },
+ { name: 'safety_level', type: 'select', description: 'Niveau de sécurité : standard (pas de LLM) ou medical_critical (LLM contextuel)' },
+ { name: 'safety_checks', type: 'safety_checks_editor', description: 'Liste de checks à valider avant reprise (id, libellé, obligatoire ?). Édité dans le panneau Propriétés.' }
] },
{ type: 't2a_decision', label: 'Décision T2A (LLM)', icon: '🧠', description: 'Analyse un DPI urgences via LLM local (qwen2.5:7b par défaut) et propose FORFAIT_URGENCE ou REQUALIFICATION_HOSPITALISATION. Retourne JSON {decision, justification, elements_pour/contre, confiance}. Bench validé 100% accuracy.', category: 'logic', needsAnchor: false, params: [
{ name: 'input_template', type: 'string', description: 'DPI à analyser. Supporte le templating {{var}} pour concaténer plusieurs extractions (ex: "{{texte_motif}}\\n{{texte_examens}}\\n{{texte_notes}}")' },
@@ -312,13 +325,19 @@ export interface WorkflowSummary {
export interface Execution {
id: string;
workflow_id: string;
- status: 'pending' | 'running' | 'paused' | 'completed' | 'error' | 'cancelled';
+ status: 'pending' | 'running' | 'paused' | 'paused_need_help' | 'completed' | 'error' | 'cancelled';
progress: number;
current_step_index: number;
completed_steps: number;
failed_steps: number;
total_steps: number;
error_message?: string;
+ // === QW4 — Pause supervisée (renvoyés par /replay/state quand status = paused_need_help) ===
+ pause_reason?: string;
+ pause_message?: string;
+ safety_checks?: SafetyCheck[];
+ // ID du replay (utile pour appeler /replay/resume avec acknowledged_check_ids)
+ replay_id?: string;
}
export interface Session {