TTS-Arena-V2 / templates /admin /user_detail.html
GitHub Actions
Sync from GitHub repo
5582677
{% 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 %}