Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
{% extends "admin/base.html" %} | |
{% block admin_content %} | |
<div class="admin-header"> | |
<div class="admin-title">User Details</div> | |
<a href="{{ url_for('admin.users') }}" class="btn-secondary">Back to Users</a> | |
</div> | |
<div class="admin-card"> | |
<div class="admin-card-header"> | |
<div class="admin-card-title">User Information</div> | |
</div> | |
<div class="user-info"> | |
<div class="user-detail-row"> | |
<div class="user-detail-label">Username:</div> | |
<div class="user-detail-value">{{ user.username }}</div> | |
</div> | |
<div class="user-detail-row"> | |
<div class="user-detail-label">Hugging Face ID:</div> | |
<div class="user-detail-value">{{ user.hf_id }}</div> | |
</div> | |
<div class="user-detail-row"> | |
<div class="user-detail-label">Join Date:</div> | |
<div class="user-detail-value">{{ user.join_date.strftime('%Y-%m-%d %H:%M:%S') if user.join_date else 'N/A' }}</div> | |
</div> | |
<div class="user-detail-row"> | |
<div class="user-detail-label">HF Account Created:</div> | |
<div class="user-detail-value">{{ user.hf_account_created.strftime('%Y-%m-%d %H:%M:%S') if user.hf_account_created else 'N/A' }}</div> | |
</div> | |
</div> | |
<div class="user-stats"> | |
<div class="stat-card"> | |
<div class="stat-title">Total Votes</div> | |
<div class="stat-value">{{ total_votes }}</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-title">TTS Votes</div> | |
<div class="stat-value">{{ tts_votes }}</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-title">Conversational Votes</div> | |
<div class="stat-value">{{ conversational_votes }}</div> | |
</div> | |
<div class="stat-card security-score-card"> | |
<div class="stat-title">Security Score</div> | |
<div class="stat-value"> | |
{% if security_score < 20 %} | |
<span class="security-score high-risk">{{ security_score }}/100</span> | |
{% elif security_score < 40 %} | |
<span class="security-score medium-risk">{{ security_score }}/100</span> | |
{% elif security_score < 70 %} | |
<span class="security-score low-risk">{{ security_score }}/100</span> | |
{% else %} | |
<span class="security-score trusted">{{ security_score }}/100</span> | |
{% endif %} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="admin-card"> | |
<div class="admin-card-header"> | |
<div class="admin-card-title">Security Analysis</div> | |
</div> | |
<div class="security-factors"> | |
{% if security_factors.account_age_days is not none %} | |
<div class="security-factor"> | |
<div class="factor-label">Account Age:</div> | |
<div class="factor-value">{{ security_factors.account_age_days }} days</div> | |
</div> | |
{% endif %} | |
{% if security_factors.hf_account_age_days is not none %} | |
<div class="security-factor"> | |
<div class="factor-label">HF Account Age:</div> | |
<div class="factor-value">{{ security_factors.hf_account_age_days }} days</div> | |
</div> | |
{% endif %} | |
<div class="security-factor"> | |
<div class="factor-label">Recent Vote Count (24h):</div> | |
<div class="factor-value">{{ security_factors.recent_vote_count or 0 }}</div> | |
</div> | |
<div class="security-factor"> | |
<div class="factor-label">Total Votes:</div> | |
<div class="factor-value">{{ security_factors.total_votes or 0 }}</div> | |
</div> | |
{% if security_factors.avg_vote_interval %} | |
<div class="security-factor"> | |
<div class="factor-label">Avg Vote Interval:</div> | |
<div class="factor-value">{{ "%.1f"|format(security_factors.avg_vote_interval) }}s</div> | |
</div> | |
{% endif %} | |
<div class="security-factor"> | |
<div class="factor-label">Suspicious Voting:</div> | |
<div class="factor-value"> | |
{% if security_factors.suspicious_voting %} | |
<span class="status-bad">Yes</span> | |
{% if security_factors.suspicious_reason %} | |
<div class="factor-detail">{{ security_factors.suspicious_reason }}</div> | |
{% endif %} | |
{% else %} | |
<span class="status-good">No</span> | |
{% endif %} | |
</div> | |
</div> | |
<div class="security-factor"> | |
<div class="factor-label">Rapid Voting:</div> | |
<div class="factor-value"> | |
{% if security_factors.rapid_voting %} | |
<span class="status-bad">Yes</span> | |
{% else %} | |
<span class="status-good">No</span> | |
{% endif %} | |
</div> | |
</div> | |
{% if security_factors.max_bias_ratio is defined %} | |
<div class="security-factor"> | |
<div class="factor-label">Max Model Bias:</div> | |
<div class="factor-value"> | |
{{ "%.1f"|format(security_factors.max_bias_ratio * 100) }}% | |
{% if security_factors.bias_penalty %} | |
<div class="factor-detail">{{ security_factors.bias_penalty }}</div> | |
{% endif %} | |
</div> | |
</div> | |
{% endif %} | |
</div> | |
</div> | |
<!-- User Timeout Information --> | |
<div class="admin-card"> | |
<div class="admin-card-header"> | |
<div class="admin-card-title">Timeout Status</div> | |
</div> | |
<div class="timeout-status" data-user-id="{{ user.id }}"> | |
<div class="loading-status">Checking timeout status...</div> | |
</div> | |
</div> | |
{% if recent_votes %} | |
<div class="admin-card"> | |
<div class="admin-card-header"> | |
<div class="admin-card-title">Recent Votes</div> | |
</div> | |
<div class="table-responsive"> | |
<table class="admin-table"> | |
<thead> | |
<tr> | |
<th>Date</th> | |
<th>Type</th> | |
<th>Chosen Model</th> | |
<th>Rejected Model</th> | |
<th>Text</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for vote in recent_votes %} | |
<tr> | |
<td>{{ vote.vote_date.strftime('%Y-%m-%d %H:%M') }}</td> | |
<td>{{ vote.model_type }}</td> | |
<td>{{ vote.chosen.name }}</td> | |
<td>{{ vote.rejected.name }}</td> | |
<td> | |
<div class="text-truncate" title="{{ vote.text }}"> | |
{{ vote.text }} | |
</div> | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
{% endif %} | |
{% if model_bias_analysis %} | |
<div class="admin-card"> | |
<div class="admin-card-header"> | |
<div class="admin-card-title">Model Bias Analysis</div> | |
<div class="admin-card-subtitle">Shows how often each model was chosen vs how often it appeared in comparisons</div> | |
</div> | |
<div class="table-responsive"> | |
<table class="admin-table"> | |
<thead> | |
<tr> | |
<th>Model</th> | |
<th>Chosen</th> | |
<th>Appeared</th> | |
<th>Bias Ratio</th> | |
<th>Bias Level</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for model_stats in model_bias_analysis %} | |
<tr> | |
<td>{{ model_stats.name }}</td> | |
<td>{{ model_stats.chosen }}</td> | |
<td>{{ model_stats.appeared }}</td> | |
<td>{{ "%.1f"|format(model_stats.bias_ratio * 100) }}%</td> | |
<td> | |
{% if model_stats.appeared < 5 %} | |
<span class="bias-badge insufficient-data">Too Few Votes</span> | |
{% elif model_stats.bias_ratio >= 0.9 and model_stats.appeared >= 10 %} | |
<span class="bias-badge extreme-bias">Extreme Bias</span> | |
{% elif model_stats.bias_ratio >= 0.8 and model_stats.appeared >= 8 %} | |
<span class="bias-badge high-bias">High Bias</span> | |
{% elif model_stats.bias_ratio >= 0.7 and model_stats.appeared >= 5 %} | |
<span class="bias-badge moderate-bias">Moderate Bias</span> | |
{% elif model_stats.bias_ratio >= 0.6 and model_stats.appeared >= 5 %} | |
<span class="bias-badge low-bias">Low Bias</span> | |
{% else %} | |
<span class="bias-badge no-bias">Normal Pattern</span> | |
{% endif %} | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
{% endif %} | |
<style> | |
.user-detail-row { | |
display: flex; | |
margin-bottom: 10px; | |
} | |
.user-detail-label { | |
font-weight: 600; | |
min-width: 150px; | |
} | |
.user-detail-value { | |
flex: 1; | |
} | |
.user-stats { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); | |
gap: 16px; | |
margin-top: 24px; | |
} | |
.security-score-card { | |
border: 2px solid #e9ecef; | |
} | |
.security-score { | |
font-weight: bold; | |
font-size: 1.1em; | |
} | |
.security-score.high-risk { | |
color: #dc3545; | |
} | |
.security-score.medium-risk { | |
color: #fd7e14; | |
} | |
.security-score.low-risk { | |
color: #ffc107; | |
} | |
.security-score.trusted { | |
color: #28a745; | |
} | |
.security-factors { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 16px; | |
margin-top: 16px; | |
} | |
.security-factor { | |
display: flex; | |
flex-direction: column; | |
padding: 12px; | |
background-color: #f8f9fa; | |
border-radius: 6px; | |
border-left: 4px solid #dee2e6; | |
} | |
.factor-label { | |
font-weight: 600; | |
margin-bottom: 4px; | |
color: #495057; | |
} | |
.factor-value { | |
font-size: 1.1em; | |
} | |
.factor-detail { | |
font-size: 0.9em; | |
color: #6c757d; | |
margin-top: 4px; | |
font-style: italic; | |
} | |
.status-good { | |
color: #28a745; | |
font-weight: 600; | |
} | |
.status-bad { | |
color: #dc3545; | |
font-weight: 600; | |
} | |
.bias-badge { | |
padding: 4px 8px; | |
border-radius: 4px; | |
font-size: 0.85em; | |
font-weight: 600; | |
text-transform: uppercase; | |
} | |
.bias-badge.extreme-bias { | |
background-color: #dc3545; | |
color: white; | |
} | |
.bias-badge.high-bias { | |
background-color: #fd7e14; | |
color: white; | |
} | |
.bias-badge.moderate-bias { | |
background-color: #ffc107; | |
color: black; | |
} | |
.bias-badge.low-bias { | |
background-color: #17a2b8; | |
color: white; | |
} | |
.bias-badge.no-bias { | |
background-color: #28a745; | |
color: white; | |
} | |
.bias-badge.insufficient-data { | |
background-color: #6c757d; | |
color: white; | |
} | |
.text-truncate { | |
max-width: 150px; | |
} | |
.admin-stats { | |
grid-template-columns: 1fr; | |
} | |
} | |
.timeout-status { | |
padding: 16px; | |
border-radius: var(--radius); | |
border: 1px solid var(--border-color); | |
} | |
.timeout-active { | |
background-color: #f8d7da; | |
border-color: #f1aeb5; | |
color: #721c24; | |
} | |
.timeout-none { | |
background-color: #d1edff; | |
border-color: #9fccff; | |
color: #004085; | |
} | |
.timeout-expired { | |
background-color: #fff3cd; | |
border-color: #ffeaa7; | |
color: #856404; | |
} | |
.timeout-details { | |
margin-top: 12px; | |
padding: 12px; | |
background-color: rgba(0,0,0,0.05); | |
border-radius: 4px; | |
font-size: 14px; | |
} | |
.timeout-actions { | |
margin-top: 12px; | |
display: flex; | |
gap: 8px; | |
} | |
.loading-status { | |
color: #666; | |
font-style: italic; | |
} | |
</style> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Check user timeout status | |
const timeoutStatusDiv = document.querySelector('.timeout-status'); | |
const userId = timeoutStatusDiv.dataset.userId; | |
// Since we don't have a direct API endpoint for timeout status, | |
// we'll simulate the check for now. In a real implementation, | |
// you would make an AJAX call to check the user's timeout status. | |
// For demonstration, let's show that no timeout is active | |
setTimeout(() => { | |
timeoutStatusDiv.innerHTML = ` | |
<div class="timeout-none"> | |
<strong>✅ No Active Timeout</strong> | |
<div style="margin-top: 8px;"> | |
This user is not currently timed out and can vote normally. | |
</div> | |
<div class="timeout-actions"> | |
<a href="{{ url_for('admin.timeouts') }}" class="action-btn">Manage Timeouts</a> | |
</div> | |
</div> | |
`; | |
}, 1000); | |
}); | |
</script> | |
{% endblock %} |