|
|
<!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"> |
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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; |
|
|
|
|
|
|
|
|
displayResults(data, text); |
|
|
|
|
|
|
|
|
displayComparison(text, data); |
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
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(''); |
|
|
} |
|
|
|
|
|
|
|
|
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> |
|
|
|