← back to Dw Boardroom Governance

frontend/src/components/AutonomyControl.tsx

99 lines

import { useState, useEffect } from 'react';
import { api } from '../api';

export default function AutonomyControl() {
  const [config, setConfig] = useState<Record<string, any>>({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    api.getConfig().then(c => { setConfig(c); setLoading(false); }).catch(() => setLoading(false));
  }, []);

  async function updateSetting(key: string, value: any) {
    const previous = config;
    const updated = { ...config, [key]: value };
    setConfig(updated);
    try {
      await api.updateConfig({ [key]: value });
    } catch (err: any) {
      console.error('[AutonomyControl] updateConfig failed:', err.message);
      setConfig(previous); // revert optimistic update on failure
    }
  }

  const mode = (typeof config.governance_mode === 'string' ? config.governance_mode : 'supervised').replace(/"/g, '');
  const autonomyDefault = Number(config.autonomy_default) || 1;
  const threshold = Number(config.escalation_threshold_hours) || 12;

  if (loading) return <div style={styles.card}><div style={styles.empty}>Loading config...</div></div>;

  return (
    <div style={styles.card}>
      <h3 style={styles.cardTitle}>Governance Controls</h3>

      {/* Mode toggle */}
      <div style={styles.setting}>
        <div style={styles.settingLabel}>Governance Mode</div>
        <div style={styles.toggleRow}>
          <button
            style={{ ...styles.modeBtn, ...(mode === 'supervised' ? styles.modeBtnActive : {}) }}
            onClick={() => updateSetting('governance_mode', 'supervised')}
          >
            Supervised
          </button>
          <button
            style={{ ...styles.modeBtn, ...(mode === 'autonomous' ? { ...styles.modeBtnActive, background: '#22c55e33', borderColor: '#22c55e', color: '#22c55e' } : {}) }}
            onClick={() => updateSetting('governance_mode', 'autonomous')}
          >
            Autonomous
          </button>
        </div>
        <div style={styles.hint}>
          {mode === 'supervised' ? 'All decisions require human approval.' : 'Type A decisions auto-execute at autonomy level 4+.'}
        </div>
      </div>

      {/* Default autonomy */}
      <div style={styles.setting}>
        <div style={styles.settingLabel}>Default Autonomy Level: {autonomyDefault}</div>
        <input type="range" min={1} max={5} value={autonomyDefault}
          onChange={e => updateSetting('autonomy_default', Number(e.target.value))}
          style={{ width: '100%' }}
        />
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
          <span>1 — Full oversight</span>
          <span>5 — Full autonomy</span>
        </div>
      </div>

      {/* Escalation threshold */}
      <div style={styles.setting}>
        <div style={styles.settingLabel}>Escalation Threshold: {threshold}h</div>
        <input type="range" min={1} max={48} value={threshold}
          onChange={e => updateSetting('escalation_threshold_hours', Number(e.target.value))}
          style={{ width: '100%' }}
        />
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
          <span>1 hour</span>
          <span>48 hours</span>
        </div>
        <div style={styles.hint}>
          Pending decisions older than {threshold}h are auto-escalated to Type C (human-required).
        </div>
      </div>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
  cardTitle: { margin: '0 0 20px', fontSize: 15, fontWeight: 700 },
  setting: { marginBottom: 24 },
  settingLabel: { fontSize: 12, fontWeight: 700, marginBottom: 8, color: '#e8eaf0' },
  toggleRow: { display: 'flex', gap: 8 },
  modeBtn: { padding: '8px 20px', borderRadius: 8, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.2s' },
  modeBtnActive: { background: '#8b5cf633', borderColor: '#8b5cf6', color: '#8b5cf6' },
  hint: { fontSize: 11, color: '#7a7f96', marginTop: 6, fontStyle: 'italic' },
  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
};