Download frontend/src/components/ManualQueryForm.jsx from WizardCoder2007/aegis-fraud-detector: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/WizardCoder2007/aegis-fraud-detector/resolve/main/frontend/src/components/ManualQueryForm.jsx
- Command line
-
hf download hf://spaces/WizardCoder2007/aegis-fraud-detector/frontend/src/components/ManualQueryForm.jsx
-
curl -L -o ManualQueryForm.jsx https://huggingface.co/spaces/WizardCoder2007/aegis-fraud-detector/resolve/main/frontend/src/components/ManualQueryForm.jsx
10 kB
| import React, { useState } from 'react'; | |
| import { Shield, ShieldAlert, ShieldCheck, RefreshCw, Zap } from 'lucide-react'; | |
| // Preset values for testing | |
| const PRESETS = { | |
| legitimate: { | |
| F3888: '2026-06-08 10:00:00', // Date | |
| F3886: 'Savings', // Account Type | |
| F3887: '170', // Account Code | |
| F3889: 'G365D', // Customer Code | |
| F3890: 'R', // Account State | |
| F3891: 'salaried', // Job Status | |
| F3892: 'M', // Gender | |
| F3893: 'RETAIL', // Channel | |
| F3894: '30', // Tx Interval | |
| F3895: '600', // Tx Amount | |
| F3896: '600', // Daily Limit | |
| F3897: '0' | |
| }, | |
| suspicious: { | |
| // For fraud, we set features that would tip off the model, e.g. very short interval, high amount, unemployed, etc. | |
| F3888: '2026-06-08 23:45:00', // Late night | |
| F3886: 'Savings', | |
| F3887: '999', // Different account code | |
| F3889: 'X999Z', | |
| F3890: 'S', // Suspicious state | |
| F3891: 'unemployed', // Unemployed job status | |
| F3892: 'F', | |
| F3893: 'INTERNET', // Internet channel | |
| F3894: '1', // Tx Interval of 1 second (very high speed!) | |
| F3895: '50000', // High transaction amount | |
| F3896: '100000', // High limit | |
| F3897: '1' // Alarm flag active | |
| } | |
| }; | |
| export default function ManualQueryForm({ onSubmit, modelName, threshold }) { | |
| const [formData, setFormData] = useState(PRESETS.legitimate); | |
| const [loading, setLoading] = useState(false); | |
| const [result, setResult] = useState(null); | |
| const handleInputChange = (key, value) => { | |
| setFormData(prev => ({ | |
| ...prev, | |
| [key]: value | |
| })); | |
| }; | |
| const loadPreset = (type) => { | |
| setFormData(PRESETS[type]); | |
| setResult(null); | |
| }; | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| setLoading(true); | |
| setResult(null); | |
| try { | |
| // Map form string numbers to float/int where appropriate | |
| const mappedData = {}; | |
| Object.keys(formData).forEach(key => { | |
| const val = formData[key]; | |
| if (!isNaN(val) && val.trim() !== '') { | |
| mappedData[key] = val.includes('.') ? parseFloat(val) : parseInt(val); | |
| } else { | |
| mappedData[key] = val; | |
| } | |
| }); | |
| const res = await onSubmit(mappedData); | |
| setResult(res); | |
| } catch (err) { | |
| console.error(err); | |
| alert('Prediction query failed. Ensure backend is running.'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="glass-card animate-fade-in" style={{ height: '100%' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> | |
| <Shield style={{ color: 'var(--primary)' }} /> | |
| <h2 style={{ fontSize: '1.25rem', margin: 0 }}>Real-Time Inspector</h2> | |
| </div> | |
| <div style={{ display: 'flex', gap: '8px' }}> | |
| <button | |
| type="button" | |
| className="btn btn-secondary" | |
| style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--success)' }} | |
| onClick={() => loadPreset('legitimate')} | |
| > | |
| Safe Preset | |
| </button> | |
| <button | |
| type="button" | |
| className="btn btn-secondary" | |
| style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--danger)' }} | |
| onClick={() => loadPreset('suspicious')} | |
| > | |
| Suspicious Preset | |
| </button> | |
| </div> | |
| </div> | |
| <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}> | |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> | |
| <div> | |
| <label className="form-label">Date-Time (F3888)</label> | |
| <input | |
| type="text" | |
| className="form-input" | |
| value={formData.F3888} | |
| onChange={(e) => handleInputChange('F3888', e.target.value)} | |
| required | |
| /> | |
| </div> | |
| <div> | |
| <label className="form-label">Account Type (F3886)</label> | |
| <select | |
| className="form-input" | |
| value={formData.F3886} | |
| onChange={(e) => handleInputChange('F3886', e.target.value)} | |
| > | |
| <option value="Savings">Savings</option> | |
| <option value="Current">Current</option> | |
| <option value="Salary">Salary</option> | |
| <option value="Corporate">Corporate</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="form-label">Channel (F3893)</label> | |
| <select | |
| className="form-input" | |
| value={formData.F3893} | |
| onChange={(e) => handleInputChange('F3893', e.target.value)} | |
| > | |
| <option value="RETAIL">Retail / Branch</option> | |
| <option value="INTERNET">Internet Banking</option> | |
| <option value="MOBILE">Mobile App</option> | |
| <option value="ATM">ATM Withdrawal</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="form-label">Job Status (F3891)</label> | |
| <select | |
| className="form-input" | |
| value={formData.F3891} | |
| onChange={(e) => handleInputChange('F3891', e.target.value)} | |
| > | |
| <option value="salaried">Salaried Employee</option> | |
| <option value="selfemployed">Self Employed</option> | |
| <option value="student">Student</option> | |
| <option value="unemployed">Unemployed</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="form-label">Tx Interval Sec (F3894)</label> | |
| <input | |
| type="number" | |
| className="form-input" | |
| value={formData.F3894} | |
| onChange={(e) => handleInputChange('F3894', e.target.value)} | |
| required | |
| /> | |
| </div> | |
| <div> | |
| <label className="form-label">Tx Amount (F3895)</label> | |
| <input | |
| type="number" | |
| className="form-input" | |
| value={formData.F3895} | |
| onChange={(e) => handleInputChange('F3895', e.target.value)} | |
| required | |
| /> | |
| </div> | |
| <div> | |
| <label className="form-label">Daily Limit (F3896)</label> | |
| <input | |
| type="number" | |
| className="form-input" | |
| value={formData.F3896} | |
| onChange={(e) => handleInputChange('F3896', e.target.value)} | |
| required | |
| /> | |
| </div> | |
| <div> | |
| <label className="form-label">Account State (F3890)</label> | |
| <input | |
| type="text" | |
| className="form-input" | |
| value={formData.F3890} | |
| onChange={(e) => handleInputChange('F3890', e.target.value)} | |
| required | |
| /> | |
| </div> | |
| </div> | |
| <button | |
| type="submit" | |
| className="btn btn-primary" | |
| style={{ width: '100%', padding: '12px', marginTop: '8px' }} | |
| disabled={loading} | |
| > | |
| {loading ? ( | |
| <> | |
| <RefreshCw size={18} style={{ animation: 'spin 1.5s linear infinite' }} /> | |
| Running AI Risk Check... | |
| </> | |
| ) : ( | |
| <> | |
| <Zap size={18} /> | |
| Evaluate Transaction | |
| </> | |
| )} | |
| </button> | |
| </form> | |
| {/* Real-time result display */} | |
| {result && ( | |
| <div className="animate-fade-in" style={{ | |
| marginTop: '20px', | |
| padding: '16px', | |
| borderRadius: '10px', | |
| background: result.is_suspicious ? 'rgba(239, 68, 68, 0.08)' : 'rgba(16, 185, 129, 0.08)', | |
| border: `1px solid ${result.is_suspicious ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)'}` | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}> | |
| <span style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--text-secondary)' }}>DECISION RESULT</span> | |
| {result.is_suspicious ? ( | |
| <span className="badge badge-danger" style={{ display: 'flex', gap: '4px' }}> | |
| <ShieldAlert size={12} /> SUSPICIOUS | |
| </span> | |
| ) : ( | |
| <span className="badge badge-success" style={{ display: 'flex', gap: '4px' }}> | |
| <ShieldCheck size={12} /> LEGITIMATE | |
| </span> | |
| )} | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '8px' }}> | |
| <span style={{ fontSize: '2rem', fontWeight: 800, color: result.is_suspicious ? 'var(--danger)' : 'var(--success)' }}> | |
| {Math.round(result.probability * 100)}% | |
| </span> | |
| <span style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Mule / Fraud Risk Score</span> | |
| </div> | |
| <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', lineHeight: '1.4' }}> | |
| {result.is_suspicious ? ( | |
| <strong>⚠️ Threat Warning:</strong> | |
| ) : ( | |
| <strong>✅ Normal activity:</strong> | |
| )}{' '} | |
| The system evaluated this transaction as{' '} | |
| <span style={{ color: result.is_suspicious ? 'var(--danger)' : 'var(--success)', fontWeight: 600 }}> | |
| {result.risk_level} Risk | |
| </span>{' '} | |
| using the {modelName} model at threshold {threshold}. | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |