Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>censor · Aesthetic Computer</title> <link rel="icon" href="https://aesthetic.computer/icon/128x128/prompt.png" type="image/png" /> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
::-webkit-scrollbar { display: none; }
html { cursor: url("/aesthetic.computer/cursors/precise.svg") 12 12, auto; }
body { font-family: monospace; font-size: 22px; -webkit-text-size-adjust: none; min-height: 100vh; margin: 0; padding: 16px; }
.container { max-width: 800px; margin: 0 auto; }
h1 { font-weight: normal; font-size: 22px; margin: 0 0 0.5em 0; }
h1 a, h1 a:visited { text-decoration: none; }
.subtitle { margin-bottom: 1.5em; font-size: 18px; opacity: 0.7; }
.test-section { margin-bottom: 1em; }
label { display: block; font-weight: normal; margin-bottom: 0.5em; }
textarea { width: 100%; padding: 12px; border: 1px solid; border-radius: 2px; font-family: monospace; font-size: 18px; resize: vertical; min-height: 100px; }
textarea:focus { outline: none; }
.button-group { display: flex; gap: 8px; margin-bottom: 1.5em; }
button { flex: 1; padding: 12px; border: 1px solid; border-radius: 2px; font-size: 18px; font-family: monospace; cursor: pointer; transition: opacity 0.2s; }
button:hover { opacity: 0.8; }
button:active { opacity: 0.6; }
.btn-test { background: rgb(92, 205, 155); color: rgb(25, 0, 25); border-color: rgb(92, 205, 155); }
.btn-clear { background: transparent; border-color: rgba(255, 255, 255, 0.3); }
.result { padding: 16px; border-radius: 4px; margin-bottom: 1.5em; display: none; animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.result-icon { font-size: 2em; margin-bottom: 0.3em; }
.result-title { font-size: 1.2em; margin-bottom: 0.5em; }
.result-details { opacity: 0.8; margin-bottom: 0.5em; font-size: 16px; }
.result-sentiment { padding: 8px; border-radius: 2px; font-size: 14px; margin-top: 0.5em; opacity: 0.7; }
.loading { display: none; text-align: center; padding: 1.5em; }
.spinner { width: 32px; height: 32px; margin: 0 auto 0.5em; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.model-info { padding: 12px; border-radius: 4px; margin-bottom: 1.5em; font-size: 14px; opacity: 0.8; }
.syntax-highlighted { font-family: monospace; font-size: 13px; }
/* Dark mode */ @media (prefers-color-scheme: dark) { body { background-color: rgb(64, 56, 74); color: rgba(255, 255, 255, 0.85); }
h1 a, h1 a:visited { color: rgba(255, 255, 255, 0.85); }
h1 a:hover { color: rgb(205, 92, 155); }
textarea { background: rgb(25, 0, 25); color: rgba(255, 255, 255, 0.85); border-color: rgba(205, 92, 155, 0.3); }
textarea:focus { border-color: rgb(205, 92, 155); }
button { background: rgb(25, 0, 25); color: rgba(255, 255, 255, 0.85); border-color: rgba(205, 92, 155, 0.3); }
button:hover { border-color: rgb(205, 92, 155); }
.btn-test { background: rgb(92, 205, 155); color: rgb(25, 0, 25); border-color: rgb(92, 205, 155); }
.btn-clear { background: transparent; border-color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.85); }
.result.pass { background: rgba(92, 205, 155, 0.1); border: 1px solid rgba(92, 205, 155, 0.5); }
.result.fail { background: rgba(205, 92, 92, 0.1); border: 1px solid rgba(205, 92, 92, 0.5); }
.result.pass .result-title { color: rgb(92, 205, 155); }
.result.fail .result-title { color: rgb(205, 92, 92); }
.result-sentiment { background: rgb(25, 0, 25); }
.spinner { border: 2px solid rgba(205, 92, 155, 0.2); border-top-color: rgb(205, 92, 155); border-radius: 50%; }
.model-info { background: rgba(205, 92, 155, 0.05); border: 1px solid rgba(205, 92, 155, 0.2); } }
/* Light mode */ @media (prefers-color-scheme: light) { body { background-color: rgba(244, 235, 250); color: rgb(64, 56, 74); }
h1 a, h1 a:visited { color: rgb(64, 56, 74); }
h1 a:hover { color: rgb(205, 92, 155); }
textarea { background: white; border-color: rgba(64, 56, 74, 0.2); }
textarea:focus { border-color: rgb(205, 92, 155); }
button { background: white; border-color: rgba(64, 56, 74, 0.2); }
button:hover { border-color: rgb(205, 92, 155); }
.btn-test { background: rgb(92, 205, 155); color: white; border-color: rgb(92, 205, 155); }
.btn-clear { background: transparent; border-color: rgba(64, 56, 74, 0.2); color: rgb(64, 56, 74); }
.result.pass { background: rgba(76, 175, 80, 0.08); border: 1px solid rgba(76, 175, 80, 0.3); }
.result.fail { background: rgba(244, 67, 54, 0.08); border: 1px solid rgba(244, 67, 54, 0.3); }
.result.pass .result-title { color: rgb(46, 125, 50); }
.result.fail .result-title { color: rgb(198, 40, 40); }
.result-sentiment { background: rgba(0, 0, 0, 0.03); }
.spinner { border: 2px solid rgba(205, 92, 155, 0.2); border-top-color: rgb(205, 92, 155); border-radius: 50%; }
.model-info { background: rgba(205, 92, 155, 0.03); border: 1px solid rgba(205, 92, 155, 0.15); } } </style></head><body> <div class="container"> <h1><a href="/">censor</a></h1> <p class="subtitle">pg-13 content filter</p>
<div class="test-section"> <label for="messageInput">enter something inappropriate</label> <textarea id="messageInput" placeholder="type or paste a message..." ></textarea> </div>
<div class="button-group"> <button class="btn-test" onclick="testMessage()">test</button> <button class="btn-clear" onclick="clearForm()">clear</button> </div>
<div class="loading" id="loading"> <div class="spinner"></div> <div>testing...</div> </div>
<div class="result" id="result"></div>
<div class="model-info"> gemma2:2b · 250mb ram · ~1.3s/msg </div>
<div style="margin-top: 2em; padding-top: 2em; border-top: 1px solid rgba(205, 92, 155, 0.3);"> <h2 style="font-weight: normal; font-size: 22px; margin-bottom: 0.5em;">auto-test</h2> <p style="opacity: 0.7; margin-bottom: 1em; font-size: 16px;">test against real chat messages from mongodb</p> <div class="button-group"> <button class="btn-test" onclick="startAutoTest()">start test</button> <button class="btn-clear" onclick="stopAutoTest()">stop</button> </div>
<div id="autoTestStatus" style="margin-top: 1em; padding: 12px; border-radius: 4px; display: none;"></div> <div id="autoTestResults" style="margin-top: 1em;"></div> </div> </div>
<script> // WebSocket connection const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; let ws = null; let reconnectTimer = null;
function connectWebSocket() { ws = new WebSocket(`${protocol}//localhost:3000/ws`); ws.onopen = () => { console.log('🟢 WebSocket connected'); }; ws.onclose = () => { console.log('🔴 WebSocket disconnected'); // Reconnect after 2 seconds reconnectTimer = setTimeout(connectWebSocket, 2000); }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; }
connectWebSocket();
function clearForm() { document.getElementById('messageInput').value = ''; document.getElementById('result').style.display = 'none'; }
async function testMessage() { const message = document.getElementById('messageInput').value.trim(); if (!message) { alert('Please enter a message to test'); return; }
if (!ws || ws.readyState !== WebSocket.OPEN) { console.log('WebSocket not ready, state:', ws ? ws.readyState : 'null'); if (!ws || ws.readyState === WebSocket.CLOSED) { connectWebSocket(); } // Retry after a short delay setTimeout(() => testMessage(), 1000); return; }
const loading = document.getElementById('loading'); const result = document.getElementById('result'); loading.style.display = 'block'; result.style.display = 'none';
const startTime = Date.now(); let streamedText = '';
// Set up message handler for this test ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'warming') { // Model warming up loading.innerHTML = ` <div class="spinner"></div> <div>⏳ ${data.message}</div> `; } else if (data.type === 'start') { // Test started streamedText = ''; } else if (data.type === 'chunk') { // Streaming chunk received streamedText = data.full; // Update loading display with streaming text loading.innerHTML = ` <div class="spinner"></div> <div>testing...</div> <div class="result-sentiment" style="margin-top: 0.5em;">${streamedText}_</div> `; } else if (data.type === 'complete') { // Test complete const responseTime = Date.now() - startTime;
// Show result loading.style.display = 'none'; result.className = 'result ' + (data.decision === 't' ? 'pass' : 'fail'); const decision = data.decision === 't' ? '(yes)' : '(no)'; result.innerHTML = ` <div class="result-icon">${data.decision === 't' ? '✓' : '✗'}</div> <div class="result-title">${decision}</div> ${data.reason ? ` <div class="result-details"> (why "${data.reason}") </div> ` : ''} <div class="result-sentiment"> ${data.sentiment || ''} </div> `; result.style.display = 'block'; // Apply syntax highlighting const sentimentEl = result.querySelector('.result-sentiment'); if (sentimentEl) { const text = sentimentEl.textContent; sentimentEl.innerHTML = highlightSExpression(text); } } else if (data.type === 'error') { loading.style.display = 'none'; result.className = 'result fail'; result.innerHTML = ` <div class="result-icon">✗</div> <div class="result-title">error</div> <div class="result-details"> failed to test message </div> <div class="result-sentiment"> ${data.error} </div> `; result.style.display = 'block'; } };
// Send message to WebSocket ws.send(JSON.stringify({ message })); }
// Allow Enter to submit (Shift+Enter for new line) document.getElementById('messageInput').addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); testMessage(); } });
// Auto-test functionality let autoTestMessages = []; let autoTestIndex = 0; let autoTestRunning = false; let autoTestStats = { total: 0, passed: 0, blocked: 0 };
async function startAutoTest() { if (autoTestRunning) return; const status = document.getElementById('autoTestStatus'); status.style.display = 'block'; status.style.background = 'rgba(205, 92, 155, 0.1)'; status.style.border = '1px solid rgba(205, 92, 155, 0.3)'; status.textContent = '⏳ Loading messages from MongoDB...'; try { const response = await fetch('/api/chat-messages'); autoTestMessages = await response.json(); autoTestIndex = 0; autoTestStats = { total: 0, passed: 0, blocked: 0 }; autoTestRunning = true; status.textContent = `▶️ Testing ${autoTestMessages.length} messages...`; document.getElementById('autoTestResults').innerHTML = ''; runNextAutoTest(); } catch (error) { status.textContent = `❌ Error: ${error.message}`; status.style.background = 'rgba(205, 92, 92, 0.1)'; status.style.border = '1px solid rgba(205, 92, 92, 0.5)'; } }
function stopAutoTest() { autoTestRunning = false; const status = document.getElementById('autoTestStatus'); status.textContent = `⏸️ Stopped at ${autoTestStats.total} messages (${autoTestStats.passed} passed, ${autoTestStats.blocked} blocked)`; }
async function runNextAutoTest() { if (!autoTestRunning || autoTestIndex >= autoTestMessages.length) { autoTestRunning = false; const status = document.getElementById('autoTestStatus'); status.textContent = `✅ Complete: ${autoTestStats.total} tested (${autoTestStats.passed} passed, ${autoTestStats.blocked} blocked)`; status.style.background = 'rgba(92, 205, 155, 0.1)'; status.style.border = '1px solid rgba(92, 205, 155, 0.5)'; return; }
const message = autoTestMessages[autoTestIndex]; autoTestIndex++;
if (!ws || ws.readyState !== WebSocket.OPEN) { setTimeout(runNextAutoTest, 100); return; }
// Set up one-time listener for this test const originalOnMessage = ws.onmessage; let streamedText = ''; ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'chunk') { streamedText = data.full; } else if (data.type === 'complete') { autoTestStats.total++; if (data.decision === 't') { autoTestStats.passed++; } else { autoTestStats.blocked++; }
// Add result to display with syntax highlighting const resultsDiv = document.getElementById('autoTestResults'); const resultItem = document.createElement('div'); resultItem.style.marginBottom = '8px'; resultItem.style.padding = '8px'; resultItem.style.borderRadius = '4px'; resultItem.style.fontSize = '14px'; resultItem.style.background = data.decision === 't' ? 'rgba(92, 205, 155, 0.05)' : 'rgba(205, 92, 92, 0.05)'; resultItem.style.border = data.decision === 't' ? '1px solid rgba(92, 205, 155, 0.3)' : '1px solid rgba(205, 92, 92, 0.3)'; const icon = data.decision === 't' ? '✓' : '✗'; const truncated = message.length > 60 ? message.substring(0, 60) + '...' : message; resultItem.innerHTML = ` <span style="opacity: 0.5;">${autoTestStats.total}.</span> ${icon} ${escapeHtml(truncated)} <span style="opacity: 0.7; margin-left: 8px;">${highlightSExpression(data.sentiment || '')}</span> `; resultsDiv.insertBefore(resultItem, resultsDiv.firstChild); // Keep only last 20 results while (resultsDiv.children.length > 20) { resultsDiv.removeChild(resultsDiv.lastChild); }
// Update status const status = document.getElementById('autoTestStatus'); status.textContent = `▶️ ${autoTestStats.total}/${autoTestMessages.length} (${autoTestStats.passed} ✓, ${autoTestStats.blocked} ✗)`;
// Restore original handler and continue ws.onmessage = originalOnMessage; setTimeout(runNextAutoTest, 100); // Small delay between tests } };
// Send test message ws.send(JSON.stringify({ message })); }
function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }
function highlightSExpression(text) { if (!text) return ''; // Syntax highlighting for proper s-expressions: (yes) (why "reason") or (no) (why "reason") return text // (yes) - cyan .replace(/\(yes\)/g, '<span style="color: rgb(0, 255, 255);">(yes)</span>') // (no) - orange-red .replace(/\(no\)/g, '<span style="color: rgb(255, 99, 71);">(no)</span>') // (why ...) - parentheses purple, keyword lime, string yellow .replace(/\(why\s+"([^"]*)"\)/g, function(match, reason) { return '<span style="color: rgb(147, 112, 219);">(</span>' + '<span style="color: rgb(50, 205, 50);">why</span> ' + '<span style="color: rgb(255, 215, 0);">"' + reason + '"</span>' + '<span style="color: rgb(147, 112, 219);">)</span>'; }); }
// Apply syntax highlighting to sentiment displays function applySyntaxHighlighting() { document.querySelectorAll('.result-sentiment').forEach(el => { const text = el.textContent; el.innerHTML = highlightSExpression(text); }); document.querySelectorAll('.syntax-highlighted').forEach(el => { const text = el.textContent; el.innerHTML = highlightSExpression(text); }); }
// Apply highlighting after each result const originalOnMessage = ws?.onmessage;
</script></body></html>