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