Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Risknox | GenAI Shield V2</title> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&family=Inter:wght@400;700;800&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="/static/minimalist.css"> | |
| <style> | |
| body { height: 100vh; display: flex; flex-direction: column; overflow: hidden; } | |
| .chat-area { flex: 1; overflow-y: auto; padding: 2rem; display: flex; flex-direction: column; gap: 2rem; } | |
| .msg { display: flex; gap: 1rem; max-width: 800px; width: 100%; } | |
| .msg.user { align-self: flex-end; flex-direction: row-reverse; } | |
| .msg-bubble { padding: 1rem; border: 2px solid #000; box-shadow: 4px 4px 0px 0px #000; font-size: 0.9375rem; } | |
| .msg.user .msg-bubble { background: #000; color: #fff; } | |
| .msg.assistant .msg-bubble { background: #fff; color: #000; } | |
| .msg-avatar { width: 40px; height: 40px; border: 2px solid #000; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 0.75rem; flex-shrink: 0; background: #fff; } | |
| .msg.user .msg-avatar { background: #000; color: #fff; } | |
| .input-area { padding: 2rem; border-top: 4px solid #000; } | |
| .input-row { max-width: 800px; margin: 0 auto; display: flex; gap: 1rem; } | |
| .input-wrap { flex: 1; border: 2px solid #000; padding: 1rem; box-shadow: 4px 4px 0px 0px #000; } | |
| textarea { width: 100%; border: none; outline: none; font-family: inherit; resize: none; min-height: 24px; font-size: 1rem; } | |
| .send-btn { width: 60px; height: 60px; box-shadow: 4px 4px 0px 0px #000; } | |
| .threat-badge { font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; font-weight: 800; padding: 2px 6px; border: 1px solid #000; margin-top: 0.5rem; display: inline-block; } | |
| .pg-badge { font-family: 'JetBrains Mono', monospace; font-size: 0.6rem; font-weight: 800; padding: 2px 6px; border: 1px solid #4338ca; color: #4338ca; margin-left: 0.5rem; display: inline-block; } | |
| .msg-blocked { align-self: flex-end; border: 2px solid var(--danger); box-shadow: 4px 4px 0px 0px var(--danger); padding: 1.5rem; max-width: 800px; width: 100%; margin-bottom: 1rem; } | |
| .flag-chip { font-family: 'JetBrains Mono', monospace; font-size: 0.65rem; font-weight: 800; padding: 2px 6px; border: 1px solid var(--danger); color: var(--danger); margin-right: 0.5rem; } | |
| .msg-meta { margin-top: 0.5rem; font-size: 0.7rem; color: #666; font-family: 'JetBrains Mono', monospace; } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="header" style="padding: 1rem 2rem; margin-bottom: 0;"> | |
| <div class="container" style="padding: 0; max-width: none; display: flex; justify-content: space-between; align-items: center;"> | |
| <div> | |
| <h1 style="font-size: 1.5rem; margin: 0;">Risknox <span style="text-decoration: underline;">GenAI Shield V2</span></h1> | |
| </div> | |
| <a href="#" onclick="event.preventDefault(); window.location.href='/genai-monitoring'+window.location.search;" class="btn secondary" style="font-size: 0.75rem;">Dashboard →</a> | |
| </div> | |
| </header> | |
| <div class="chat-area" id="chat-area"></div> | |
| <div class="input-area"> | |
| <div id="attachment-preview" style="display:none; max-width: 800px; margin: 0 auto 0.5rem auto; padding: 0.5rem; border: 1px solid #000; background: #f8fafc; font-size: 0.75rem; justify-content: space-between; align-items: center;"> | |
| <span id="file-info"></span> | |
| <button onclick="removeAttachment()" style="background:none; border:none; cursor:pointer; font-weight:800;">✕</button> | |
| </div> | |
| <div class="input-row"> | |
| <input type="file" id="file-input" style="display:none" onchange="handleFileSelect(event)"> | |
| <button class="send-btn" style="background:#fff;" onclick="document.getElementById('file-input').click()"> | |
| <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4s-4 1.79-4 4v12.5c0 3.31 2.69 6 6 6s6-2.69 6-6V6h-1.5z"/></svg> | |
| </button> | |
| <div class="input-wrap"> | |
| <textarea id="prompt-input" rows="1" placeholder="ENTER ADVERSARIAL PROMPT..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage();}"></textarea> | |
| </div> | |
| <button class="send-btn" id="send-btn" onclick="sendMessage()"> | |
| <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| const chatArea = document.getElementById('chat-area'); | |
| const input = document.getElementById('prompt-input'); | |
| let currentAttachment = null; | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| currentAttachment = { filename: file.name, content_b64: event.target.result.split(',')[1] }; | |
| document.getElementById('attachment-preview').style.display = 'flex'; | |
| document.getElementById('file-info').innerText = '📎 ' + file.name + ' (' + (file.size/1024).toFixed(1) + ' KB)'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function removeAttachment() { | |
| currentAttachment = null; | |
| document.getElementById('attachment-preview').style.display = 'none'; | |
| document.getElementById('file-input').value = ''; | |
| } | |
| function appendUserMsg(text, attachment) { | |
| const div = document.createElement('div'); div.className = 'msg user'; | |
| let content = esc(text); | |
| if (attachment) content += '<div style="margin-top:0.5rem;padding:0.5rem;border:1px solid #fff;font-size:0.75rem;opacity:0.8;">📎 ' + esc(attachment.filename) + ' attached</div>'; | |
| div.innerHTML = '<div class="msg-avatar">U</div><div class="msg-bubble">' + content + '</div>'; | |
| chatArea.appendChild(div); chatArea.scrollTop = chatArea.scrollHeight; | |
| } | |
| function appendAssistantMsg(text, data) { | |
| const score = data.threat_score || 0; | |
| const pgScore = data.prompt_guard_score || 0; | |
| const b = data.latency_breakdown || {guard_ms:0, model_ms:0, monitor_ms:0}; | |
| const cls = score >= 60 ? 'threat-high' : score >= 30 ? 'threat-medium' : 'threat-clean'; | |
| const label = score >= 60 ? 'HIGH RISK' : score >= 30 ? 'SUSPICIOUS' : 'CLEAN'; | |
| const div = document.createElement('div'); div.className = 'msg assistant'; | |
| div.innerHTML = '<div class="msg-avatar">AI</div><div><div class="msg-bubble">' + esc(text) + '</div><div class="msg-meta"><span class="threat-badge ' + cls + '">' + label + ' · ' + score + '</span><span class="pg-badge">PG: ' + pgScore.toFixed(3) + '</span> Total: ' + data.latency_ms + 'ms (🛡️ ' + b.guard_ms + 'ms · 🤖 ' + b.model_ms + 'ms · 🔍 ' + b.monitor_ms + 'ms)</div></div>'; | |
| chatArea.appendChild(div); chatArea.scrollTop = chatArea.scrollHeight; | |
| } | |
| function appendBlockedMsg(data) { | |
| const pgScore = data.prompt_guard_score || 0; | |
| const div = document.createElement('div'); div.className = 'msg-blocked'; | |
| div.innerHTML = '<div style="font-weight:800;font-size:1rem;margin-bottom:0.5rem">🚫 INPUT REJECTED BY PROMPT GUARD</div><div style="font-size:12px;color:#94a3b8">' + esc(data.reason) + '</div><div style="margin-top:4px"><span class="pg-badge" style="border-color:var(--danger);color:var(--danger)">PG Score: ' + pgScore.toFixed(3) + '</span></div><div style="margin-top:8px">' + (data.flags||[]).map(function(f){return '<span class="flag-chip">' + f + '</span>';}).join('') + '</div>'; | |
| chatArea.appendChild(div); chatArea.scrollTop = chatArea.scrollHeight; | |
| } | |
| async function sendMessage() { | |
| const prompt = input.value.trim(); if (!prompt && !currentAttachment) return; | |
| const attachment = currentAttachment; | |
| input.value = ''; removeAttachment(); | |
| appendUserMsg(prompt || "[File only]", attachment); | |
| const typing = document.createElement('div'); typing.className = 'msg assistant'; | |
| typing.innerHTML = '<div class="msg-avatar">AI</div><div class="msg-bubble" style="color:#999">Analyzing with Prompt Guard...</div>'; | |
| chatArea.appendChild(typing); chatArea.scrollTop = chatArea.scrollHeight; | |
| try { | |
| const res = await fetch('/genai-chat', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({prompt, attachment}) }); | |
| const data = await res.json(); typing.remove(); | |
| if (res.status === 403 || data.blocked) appendBlockedMsg(data); | |
| else appendAssistantMsg(data.response || 'Error: ' + data.error, data); | |
| } catch (e) { typing.remove(); } | |
| } | |
| function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } | |
| </script> | |
| </body> | |
| </html> | |