Unverified Commit e102a1d4 authored by utorque's avatar utorque Committed by GitHub
Browse files

Merge pull request #11 from utorque/claude/disable-ai-by-default-01FmmgB89ZD37Yua28GBWKug

Disable AI feature until teacher activates it
parents 4568d4bb 5ee42725
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -39,6 +39,10 @@ TEST_DATA_PATH = os.path.join(DATA_DIR, 'sim2_supply_chain_data_test.json')
# TiRex model (loaded lazily on first request)
_tirex_model = None

# AI activation state (disabled by default)
_ai_enabled = False
AI_PASSWORD = os.environ.get('AI_PASSWORD', 'HorloML-AI')


def get_tirex_model():
    """Load TiRex model lazily (first time only)"""
@@ -590,9 +594,36 @@ def results():
                          test_data=test_data)


@app.route('/api/ai_status', methods=['GET'])
def ai_status():
    """Get current AI activation status"""
    global _ai_enabled
    return jsonify({'enabled': _ai_enabled})


@app.route('/api/toggle_ai', methods=['POST'])
def toggle_ai():
    """Toggle AI activation with password verification"""
    global _ai_enabled

    data = request.get_json()
    password = data.get('password', '')

    if password != AI_PASSWORD:
        return jsonify({'success': False, 'error': 'Invalid password'}), 401

    _ai_enabled = not _ai_enabled
    return jsonify({'success': True, 'enabled': _ai_enabled})


@app.route('/api/tirex_forecast', methods=['GET'])
def tirex_forecast():
    """Generate AI predictions using TiRex model"""
    # Check if AI is enabled
    global _ai_enabled
    if not _ai_enabled:
        return jsonify({'error': 'AI features are currently disabled. Please ask your teacher to enable AI.'}), 403

    if not TIREX_AVAILABLE:
        return jsonify({'error': 'TiRex library not available. Please install: pip install tirex-ts torch'}), 500

+3 −1
Original line number Diff line number Diff line
@@ -4,3 +4,5 @@ services:
    ports:
      - "52999:5001"
    restart: unless-stopped
    environment:
      - AI_PASSWORD=HorloML-AI
 No newline at end of file
+131 −0
Original line number Diff line number Diff line
@@ -72,6 +72,12 @@
            </div>

            <div class="navbar-end">
                <div class="navbar-item">
                    <button class="button is-danger" id="aiToggleButton" onclick="toggleAI()" style="min-width: 150px;">
                        <span class="icon"><i class="fas fa-brain"></i></span>
                        <span id="aiToggleText">Activate AI</span>
                    </button>
                </div>
                <div class="navbar-item">
                    <a class="button is-light" href="{{ url_for('reset') }}">
                        <span class="icon"><i class="fas fa-redo"></i></span>
@@ -92,5 +98,130 @@
            </p>
        </div>
    </footer>

    <!-- Password Modal -->
    <div class="modal" id="passwordModal">
        <div class="modal-background" onclick="closePasswordModal()"></div>
        <div class="modal-card">
            <header class="modal-card-head">
                <p class="modal-card-title">Teacher Authentication Required</p>
                <button class="delete" aria-label="close" onclick="closePasswordModal()"></button>
            </header>
            <section class="modal-card-body">
                <div class="field">
                    <label class="label">Enter Password</label>
                    <div class="control has-icons-left">
                        <input class="input" type="password" id="aiPassword" placeholder="Enter teacher password">
                        <span class="icon is-small is-left">
                            <i class="fas fa-lock"></i>
                        </span>
                    </div>
                    <p class="help is-danger" id="passwordError" style="display: none;">Invalid password. Please try again.</p>
                </div>
            </section>
            <footer class="modal-card-foot">
                <button class="button is-success" onclick="submitPassword()">Submit</button>
                <button class="button" onclick="closePasswordModal()">Cancel</button>
            </footer>
        </div>
    </div>

    <script>
    // AI Toggle functionality
    let currentAIStatus = false;

    // Check AI status on page load
    async function checkAIStatus() {
        try {
            const response = await fetch('/api/ai_status');
            const data = await response.json();
            currentAIStatus = data.enabled;
            updateAIButton(currentAIStatus);
        } catch (error) {
            console.error('Failed to check AI status:', error);
        }
    }

    // Update AI button appearance
    function updateAIButton(enabled) {
        const button = document.getElementById('aiToggleButton');
        const text = document.getElementById('aiToggleText');

        if (enabled) {
            button.classList.remove('is-danger');
            button.classList.add('is-success');
            text.textContent = 'Deactivate AI';
        } else {
            button.classList.remove('is-success');
            button.classList.add('is-danger');
            text.textContent = 'Activate AI';
        }
    }

    // Open password modal
    function toggleAI() {
        document.getElementById('passwordModal').classList.add('is-active');
        document.getElementById('aiPassword').value = '';
        document.getElementById('passwordError').style.display = 'none';
        // Focus on password input
        setTimeout(() => {
            document.getElementById('aiPassword').focus();
        }, 100);
    }

    // Close password modal
    function closePasswordModal() {
        document.getElementById('passwordModal').classList.remove('is-active');
    }

    // Submit password
    async function submitPassword() {
        const password = document.getElementById('aiPassword').value;
        const errorElement = document.getElementById('passwordError');

        try {
            const response = await fetch('/api/toggle_ai', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ password: password })
            });

            const data = await response.json();

            if (response.ok && data.success) {
                currentAIStatus = data.enabled;
                updateAIButton(currentAIStatus);
                closePasswordModal();

                // Reload page to update AI button state on predict page
                if (typeof updateAIPredictButton === 'function') {
                    updateAIPredictButton(currentAIStatus);
                }
            } else {
                errorElement.style.display = 'block';
            }
        } catch (error) {
            console.error('Failed to toggle AI:', error);
            errorElement.textContent = 'Network error. Please try again.';
            errorElement.style.display = 'block';
        }
    }

    // Allow Enter key to submit password
    document.addEventListener('DOMContentLoaded', function() {
        checkAIStatus();

        const passwordInput = document.getElementById('aiPassword');
        if (passwordInput) {
            passwordInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    submitPassword();
                }
            });
        }
    });
    </script>
</body>
</html>
+34 −1
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@
                        </button>
                    </div>
                    <div class="control">
                        <button type="button" class="button is-primary" onclick="fillWithAI()" id="aiButton">
                        <button type="button" class="button is-primary" onclick="fillWithAI()" id="aiButton" disabled>
                            <span class="icon"><i class="fas fa-brain"></i></span>
                            <span>Fill with AI Model</span>
                        </button>
@@ -168,6 +168,39 @@
const watches = {{ watches | tojson }};
const lastYear = {{ last_year | tojson }};

// Update AI button state based on AI activation
function updateAIPredictButton(enabled) {
    const button = document.getElementById('aiButton');
    if (button) {
        button.disabled = !enabled;
        if (enabled) {
            button.classList.remove('is-light');
            button.classList.add('is-primary');
            button.style.opacity = '1';
            button.style.cursor = 'pointer';
        } else {
            button.classList.remove('is-primary');
            button.classList.add('is-light');
            button.style.opacity = '0.5';
            button.style.cursor = 'not-allowed';
        }
    }
}

// Check AI status on page load
async function checkAIStatusForPredict() {
    try {
        const response = await fetch('/api/ai_status');
        const data = await response.json();
        updateAIPredictButton(data.enabled);
    } catch (error) {
        console.error('Failed to check AI status:', error);
    }
}

// Call on page load
document.addEventListener('DOMContentLoaded', checkAIStatusForPredict);

function getInputs(watchId) {
    const inputs = [];
    for (let month = 1; month <= 12; month++) {