| | <!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> |
| |
|