prof-demo / src /api /enhanced_ui.html
sbicy's picture
Upload 17 files
deff797 verified
<!DOCTYPE html>
<html>
<head>
<title>Context-Aware Profanity Handler - Interactive Demo</title>
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f7;
}
.header {
max-width: 1400px;
margin: 0 auto 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40px;
border-radius: 12px;
color: white;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.header h1 { margin: 0 0 10px; font-size: 36px; }
.header p { margin: 0; opacity: 0.95; font-size: 18px; }
.container {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.panel {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.panel.full { grid-column: 1 / -1; }
h2 {
margin-top: 0;
color: #1d1d1f;
font-size: 24px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.input-group {
margin: 20px 0;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1d1d1f;
}
textarea, select {
width: 100%;
padding: 12px;
border: 2px solid #d2d2d7;
border-radius: 8px;
font-size: 15px;
font-family: inherit;
transition: border-color 0.2s;
}
textarea:focus, select:focus {
outline: none;
border-color: #667eea;
}
textarea { min-height: 120px; resize: vertical; }
.checkbox-group {
display: flex;
gap: 20px;
margin: 20px 0;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 8px;
font-weight: 500;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 14px 32px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
}
button:active {
transform: translateY(0);
}
.badge {
display: inline-block;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
margin: 4px;
}
.badge.safe { background: #d1f4e0; color: #0f5132; }
.badge.mild { background: #fff3cd; color: #997404; }
.badge.explicit { background: #f8d7da; color: #842029; }
.badge.slur { background: #f5c2c7; color: #58151c; }
.badge.threat { background: #ea868f; color: #58151c; }
.comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.comparison-item {
padding: 15px;
background: #f5f5f7;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.comparison-item h4 {
margin-top: 0;
color: #667eea;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.comparison-item pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-size: 14px;
line-height: 1.6;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.metric {
background: #f5f5f7;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.metric-value {
font-size: 32px;
font-weight: 700;
color: #667eea;
margin: 10px 0;
}
.metric-label {
font-size: 13px;
color: #86868b;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.log-viewer {
background: #1d1d1f;
color: #f5f5f7;
padding: 20px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 13px;
max-height: 400px;
overflow-y: auto;
}
.log-entry {
margin: 10px 0;
padding: 10px;
background: rgba(255,255,255,0.05);
border-radius: 4px;
}
.log-redacted {
color: #ff9f0a;
}
.log-verbatim {
color: #30d158;
}
.example-box {
background: linear-gradient(135deg, #e0e7ff 0%, #f0e7ff 100%);
padding: 20px;
border-radius: 8px;
margin: 20px 0;
border-left: 4px solid #667eea;
}
.example-box h3 {
margin-top: 0;
color: #1d1d1f;
}
.tab-container {
margin-top: 20px;
}
.tabs {
display: flex;
gap: 10px;
border-bottom: 2px solid #d2d2d7;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: none;
background: none;
font-size: 15px;
font-weight: 600;
color: #86868b;
border-bottom: 3px solid transparent;
transition: all 0.2s;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.hidden { display: none !important; }
.ai-scores {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin: 15px 0;
}
.ai-score {
background: #f5f5f7;
padding: 12px;
border-radius: 6px;
text-align: center;
}
.ai-score-label {
font-size: 11px;
color: #86868b;
text-transform: uppercase;
}
.ai-score-value {
font-size: 20px;
font-weight: 700;
color: #1d1d1f;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="header">
<h1>🧩 Context-Aware Profanity Handler</h1>
<p>Interactive demonstration of context-aware profanity detection, delexicalization, and audit logging</p>
</div>
<div class="container">
<!-- Input Panel -->
<div class="panel">
<h2>📝 Input</h2>
<div class="example-box">
<h3>💡 Example Use Case</h3>
<p><strong>Scenario:</strong> A user wants to generate a report about an asset with an explicit song title.</p>
<p><strong>Input:</strong> "Report on asset: <em>Do You Want to Fuck Me Tonight</em>"</p>
<p><strong>Context:</strong> Song Title (Entity Name)</p>
</div>
<div class="input-group">
<label for="text">Text to Analyze:</label>
<textarea id="text" placeholder="Enter text here...">Report on asset: Do You Want to Fuck Me Tonight</textarea>
</div>
<div class="input-group">
<label for="context">Content Category:</label>
<select id="context">
<option value="song_title">Song Title</option>
<option value="entity_name">Entity Name</option>
<option value="brand_name">Brand Name</option>
<option value="user_input">User Input</option>
</select>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" id="strict_mode"> Strict Mode
</label>
<label>
<input type="checkbox" id="use_ai" checked> Use AI Classifier
</label>
<label>
<input type="checkbox" id="include_explicit"> Include in Export
</label>
</div>
<button onclick="analyzeText()">🔍 Analyze Text</button>
</div>
<!-- Results Panel -->
<div class="panel">
<h2>📊 Analysis Results</h2>
<div id="results" style="color: #86868b; text-align: center; padding: 40px;">
Run an analysis to see results here
</div>
</div>
<!-- Comparison Panel -->
<div class="panel full hidden" id="comparisonPanel">
<h2>🔄 Text Comparison</h2>
<div class="comparison">
<div class="comparison-item">
<h4>📄 Original Text (Verbatim)</h4>
<pre id="originalText"></pre>
</div>
<div class="comparison-item">
<h4>✨ Delexicalized Text (Safe for AI)</h4>
<pre id="safeText"></pre>
</div>
<div class="comparison-item">
<h4>📤 Export Text (Based on Preference)</h4>
<pre id="exportText"></pre>
</div>
<div class="comparison-item">
<h4>🔍 Detected Words</h4>
<pre id="detectedWords"></pre>
</div>
</div>
</div>
<!-- Logs Panel -->
<div class="panel full hidden" id="logsPanel">
<h2>📋 Audit Logs Visualization</h2>
<div class="tab-container">
<div class="tabs">
<button class="tab active" onclick="switchTab('redacted')">📊 Redacted Logs (Analytics)</button>
<button class="tab" onclick="switchTab('verbatim')">🔐 Verbatim Logs (Compliance)</button>
</div>
<div class="tab-content active" id="redacted-tab">
<p style="color: #86868b; margin-bottom: 15px;">
<strong>Purpose:</strong> Safe for analytics and monitoring. Contains metadata without sensitive content.
</p>
<div class="log-viewer" id="redactedLogs">
<div class="log-redacted">Waiting for analysis...</div>
</div>
</div>
<div class="tab-content" id="verbatim-tab">
<p style="color: #86868b; margin-bottom: 15px;">
<strong>Purpose:</strong> Full audit trail for compliance. Access should be restricted (RBAC).
</p>
<div class="log-viewer" id="verbatimLogs">
<div class="log-verbatim">Waiting for analysis...</div>
</div>
</div>
</div>
</div>
</div>
<script>
let currentRequestId = null;
function switchTab(tabName) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tabName + '-tab').classList.add('active');
}
async function analyzeText() {
const text = document.getElementById('text').value;
const context = document.getElementById('context').value;
const strict_mode = document.getElementById('strict_mode').checked;
const use_ai = document.getElementById('use_ai').checked;
const include_explicit_in_export = document.getElementById('include_explicit').checked;
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<div style="text-align: center; padding: 20px;">⏳ Analyzing...</div>';
try {
const response = await fetch('/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, context, strict_mode, use_ai, include_explicit_in_export })
});
const data = await response.json();
currentRequestId = data.request_id;
// Display results
displayResults(data, text);
// Show comparison panel
displayComparison(text, data);
// Load and display logs
await displayLogs(data.request_id);
} catch (error) {
resultsDiv.innerHTML = `<div style="color: #d1180b;">Error: ${error.message}</div>`;
}
}
function displayResults(data, originalText) {
const resultsDiv = document.getElementById('results');
let html = '<div class="metrics">';
html += `<div class="metric">
<div class="metric-label">Profanity</div>
<div class="metric-value">${data.contains_profanity ? '⚠️' : '✅'}</div>
</div>`;
html += `<div class="metric">
<div class="metric-label">Toxicity</div>
<div class="metric-value"><span class="badge ${data.toxicity_level}">${data.toxicity_level.toUpperCase()}</span></div>
</div>`;
html += '</div>';
html += `<div style="margin: 20px 0;">
<strong>Message:</strong>
<div style="padding: 15px; background: #f5f5f7; border-radius: 8px; margin-top: 10px;">
${data.message}
</div>
</div>`;
if (data.ai_confidence) {
html += '<div style="margin: 20px 0;"><strong>AI Confidence Scores:</strong><div class="ai-scores">';
for (const [label, score] of Object.entries(data.ai_confidence)) {
html += `<div class="ai-score">
<div class="ai-score-label">${label}</div>
<div class="ai-score-value">${(score * 100).toFixed(1)}%</div>
</div>`;
}
html += '</div></div>';
}
html += `<div style="margin-top: 20px; font-size: 13px; color: #86868b;">
Request ID: <code>${data.request_id}</code>
</div>`;
resultsDiv.innerHTML = html;
}
function displayComparison(originalText, data) {
document.getElementById('comparisonPanel').classList.remove('hidden');
document.getElementById('originalText').textContent = originalText;
document.getElementById('safeText').textContent = data.safe_text;
document.getElementById('exportText').textContent = data.export_text;
document.getElementById('detectedWords').textContent =
data.detected_words && data.detected_words.length > 0
? data.detected_words.join(', ')
: 'None detected';
}
async function displayLogs(requestId) {
document.getElementById('logsPanel').classList.remove('hidden');
try {
// Load redacted logs
const redactedResponse = await fetch('/logs/redacted');
const redactedData = await redactedResponse.json();
const redactedLogs = document.getElementById('redactedLogs');
if (redactedData.logs && redactedData.logs.length > 0) {
redactedLogs.innerHTML = redactedData.logs.slice(-5).reverse().map(log => `
<div class="log-entry log-redacted">
<strong>Request ID:</strong> ${log.request_id}<br>
<strong>Timestamp:</strong> ${new Date(log.timestamp).toLocaleString()}<br>
<strong>Context:</strong> ${log.context}<br>
<strong>Profanity:</strong> ${log.contains_profanity ? 'Yes' : 'No'}<br>
<strong>Toxicity:</strong> ${log.toxicity_level}<br>
<strong>Text Hash:</strong> ${log.text_hash}<br>
<strong>Text Length:</strong> ${log.text_length} chars
</div>
`).join('');
}
// Load verbatim log for current request
const verbatimResponse = await fetch(`/logs/verbatim/${requestId}`);
const verbatimData = await verbatimResponse.json();
const verbatimLogs = document.getElementById('verbatimLogs');
if (verbatimData.request_id) {
verbatimLogs.innerHTML = `
<div class="log-entry log-verbatim">
<strong>⚠️ COMPLIANCE ACCESS ONLY ⚠️</strong><br><br>
<strong>Request ID:</strong> ${verbatimData.request_id}<br>
<strong>Timestamp:</strong> ${new Date(verbatimData.timestamp).toLocaleString()}<br>
<strong>Context:</strong> ${verbatimData.context}<br>
<strong>Original Text:</strong> ${verbatimData.original_text}<br>
<strong>Safe Text:</strong> ${verbatimData.safe_text}<br>
<strong>Profanity:</strong> ${verbatimData.contains_profanity ? 'Yes' : 'No'}<br>
<strong>Toxicity:</strong> ${verbatimData.toxicity_level}
</div>
`;
} else {
verbatimLogs.innerHTML = '<div class="log-verbatim">Verbatim logs not available or disabled</div>';
}
} catch (error) {
console.error('Error loading logs:', error);
}
}
</script>
</body>
</html>