|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI Text Detector</title> |
|
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script> |
|
<script type="module" src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js"></script> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
min-height: 100vh; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
padding: 20px; |
|
} |
|
|
|
.container { |
|
background: white; |
|
border-radius: 20px; |
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); |
|
padding: 40px; |
|
max-width: 800px; |
|
width: 100%; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.container::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
height: 5px; |
|
background: linear-gradient(90deg, #667eea, #764ba2, #f093fb, #f5576c); |
|
} |
|
|
|
h1 { |
|
text-align: center; |
|
color: #333; |
|
margin-bottom: 10px; |
|
font-size: 2.5em; |
|
font-weight: 700; |
|
} |
|
|
|
.subtitle { |
|
text-align: center; |
|
color: #666; |
|
margin-bottom: 30px; |
|
font-size: 1.1em; |
|
} |
|
|
|
.input-section { |
|
margin-bottom: 30px; |
|
} |
|
|
|
label { |
|
display: block; |
|
margin-bottom: 10px; |
|
color: #333; |
|
font-weight: 600; |
|
font-size: 1.1em; |
|
} |
|
|
|
textarea { |
|
width: 100%; |
|
height: 200px; |
|
padding: 20px; |
|
border: 2px solid #e1e5e9; |
|
border-radius: 15px; |
|
font-size: 16px; |
|
line-height: 1.6; |
|
resize: vertical; |
|
transition: all 0.3s ease; |
|
font-family: inherit; |
|
} |
|
|
|
textarea:focus { |
|
outline: none; |
|
border-color: #667eea; |
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); |
|
} |
|
|
|
.button-container { |
|
text-align: center; |
|
margin: 30px 0; |
|
} |
|
|
|
button { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
border: none; |
|
padding: 15px 40px; |
|
border-radius: 50px; |
|
font-size: 18px; |
|
font-weight: 600; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); |
|
} |
|
|
|
button:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); |
|
} |
|
|
|
button:active { |
|
transform: translateY(0); |
|
} |
|
|
|
button:disabled { |
|
background: #ccc; |
|
cursor: not-allowed; |
|
transform: none; |
|
box-shadow: none; |
|
} |
|
|
|
.result { |
|
margin-top: 30px; |
|
padding: 25px; |
|
border-radius: 15px; |
|
text-align: center; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.result.human { |
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
|
color: white; |
|
} |
|
|
|
.result.ai { |
|
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); |
|
color: white; |
|
} |
|
|
|
.result.loading { |
|
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); |
|
color: #333; |
|
} |
|
|
|
.result.error { |
|
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); |
|
color: #333; |
|
} |
|
|
|
.prediction { |
|
font-size: 2em; |
|
font-weight: 700; |
|
margin-bottom: 10px; |
|
text-transform: uppercase; |
|
} |
|
|
|
.confidence { |
|
font-size: 1.2em; |
|
margin-bottom: 10px; |
|
} |
|
|
|
.probability { |
|
font-size: 1em; |
|
opacity: 0.9; |
|
} |
|
|
|
.stats { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
|
gap: 15px; |
|
margin-top: 20px; |
|
} |
|
|
|
.stat { |
|
text-align: center; |
|
padding: 15px; |
|
background: rgba(255, 255, 255, 0.1); |
|
border-radius: 10px; |
|
backdrop-filter: blur(10px); |
|
} |
|
|
|
.stat-value { |
|
font-size: 1.5em; |
|
font-weight: 700; |
|
display: block; |
|
} |
|
|
|
.stat-label { |
|
font-size: 0.9em; |
|
opacity: 0.8; |
|
} |
|
|
|
.loading-spinner { |
|
display: inline-block; |
|
width: 20px; |
|
height: 20px; |
|
border: 2px solid #f3f3f3; |
|
border-top: 2px solid #333; |
|
border-radius: 50%; |
|
animation: spin 1s linear infinite; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
.model-info { |
|
background: #f8f9fa; |
|
padding: 20px; |
|
border-radius: 15px; |
|
margin-bottom: 30px; |
|
border-left: 5px solid #667eea; |
|
} |
|
|
|
.model-info h3 { |
|
color: #333; |
|
margin-bottom: 10px; |
|
} |
|
|
|
.model-info p { |
|
color: #666; |
|
line-height: 1.6; |
|
} |
|
|
|
.examples { |
|
margin-top: 30px; |
|
display: grid; |
|
grid-template-columns: 1fr 1fr; |
|
gap: 20px; |
|
} |
|
|
|
.example { |
|
background: #f8f9fa; |
|
padding: 15px; |
|
border-radius: 10px; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
border: 2px solid transparent; |
|
} |
|
|
|
.example:hover { |
|
background: #e9ecef; |
|
border-color: #667eea; |
|
} |
|
|
|
.example h4 { |
|
color: #333; |
|
margin-bottom: 10px; |
|
font-size: 1em; |
|
} |
|
|
|
.example p { |
|
color: #666; |
|
font-size: 0.9em; |
|
line-height: 1.4; |
|
} |
|
|
|
.status { |
|
margin-top: 10px; |
|
padding: 8px 12px; |
|
border-radius: 8px; |
|
font-size: 0.9em; |
|
font-weight: 500; |
|
} |
|
|
|
.status.loading { |
|
background: #fff3cd; |
|
color: #856404; |
|
border: 1px solid #ffeaa7; |
|
} |
|
|
|
.status.ready { |
|
background: #d4edda; |
|
color: #155724; |
|
border: 1px solid #c3e6cb; |
|
} |
|
|
|
.status.processing { |
|
background: #cce8ff; |
|
color: #004085; |
|
border: 1px solid #b3d9ff; |
|
} |
|
|
|
.status.error { |
|
background: #f8d7da; |
|
color: #721c24; |
|
border: 1px solid #f5c6cb; |
|
} |
|
|
|
.status.complete { |
|
background: #d1ecf1; |
|
color: #0c5460; |
|
border: 1px solid #bee5eb; |
|
} |
|
|
|
code { |
|
background: rgba(0,0,0,0.1); |
|
padding: 2px 4px; |
|
border-radius: 3px; |
|
font-family: monospace; |
|
font-size: 0.85em; |
|
} |
|
.container { |
|
padding: 20px; |
|
margin: 10px; |
|
} |
|
|
|
h1 { |
|
font-size: 2em; |
|
} |
|
|
|
.stats { |
|
grid-template-columns: repeat(2, 1fr); |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<h1>🤖 AI Text Detector</h1> |
|
<p class="subtitle">Powered by Ultra-Optimized Neural Networks</p> |
|
|
|
<div class="model-info"> |
|
<h3>📊 Model Status</h3> |
|
<div id="status" class="status loading">🔄 Loading model and tokenizer...</div> |
|
</div> |
|
|
|
<div class="input-section"> |
|
<label for="textInput">📝 Enter text to analyze:</label> |
|
<textarea |
|
id="textInput" |
|
placeholder="Paste your text here... (minimum 100 characters required for accurate analysis)" |
|
spellcheck="false" |
|
></textarea> |
|
</div> |
|
|
|
<div class="button-container"> |
|
<button id="analyzeBtn" onclick="analyzeText()"> |
|
<span id="btnText">🚀 Analyze Text</span> |
|
<span id="btnSpinner" class="loading-spinner" style="display: none;"></span> |
|
</button> |
|
</div> |
|
|
|
<div id="result" class="result" style="display: none;"></div> |
|
</div> |
|
|
|
<script type="module"> |
|
import { AutoTokenizer } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js'; |
|
|
|
let session = null; |
|
let tokenizer = null; |
|
|
|
|
|
async function initializeModel() { |
|
try { |
|
console.log('Loading tokenizer and ONNX model...'); |
|
|
|
|
|
tokenizer = await AutoTokenizer.from_pretrained('HuggingFaceTB/SmolLM-135M', { |
|
progress_callback: (progress) => { |
|
if (progress.status === 'downloading') { |
|
updateStatus('loading', `📥 Downloading tokenizer: ${progress.name}`); |
|
} |
|
} |
|
}); |
|
console.log('Tokenizer loaded successfully!'); |
|
updateStatus('loading', '🤖 Loading ONNX model...'); |
|
|
|
|
|
const possibleModelNames = [ |
|
'./fixed_optimized_detector.onnx', |
|
'./ultra_optimized_detector.onnx', |
|
'./optimized_detector.onnx', |
|
'./model.onnx' |
|
]; |
|
|
|
let modelLoaded = false; |
|
for (const modelPath of possibleModelNames) { |
|
try { |
|
session = await ort.InferenceSession.create(modelPath); |
|
console.log(`ONNX model loaded successfully from: ${modelPath}`); |
|
modelLoaded = true; |
|
break; |
|
} catch (error) { |
|
|
|
if (!error.message.includes('failed to load external data file')) { |
|
console.log(`Failed to load from ${modelPath}:`, error.message); |
|
} |
|
} |
|
} |
|
|
|
if (!modelLoaded) { |
|
throw new Error('ONNX model file not found. Please ensure your .onnx file is in the same directory as this HTML file.'); |
|
} |
|
|
|
console.log('Model inputs:', session.inputNames); |
|
console.log('Model outputs:', session.outputNames); |
|
|
|
|
|
document.getElementById('analyzeBtn').disabled = false; |
|
updateStatus('ready', '✅ Model loaded and ready!'); |
|
|
|
} catch (error) { |
|
console.error('Failed to load model:', error); |
|
updateStatus('error', `❌ Failed to load: ${error.message}`); |
|
|
|
|
|
if (error.message.includes('tokenizer')) { |
|
showResult('error', '❌ Failed to load tokenizer. Please check your internet connection.'); |
|
} else if (error.message.includes('ONNX') || error.message.includes('external data')) { |
|
showResult('error', `❌ ONNX model file not found. Please place your .onnx model file in the same directory as this HTML file. Expected names: ultra_optimized_detector.onnx, fixed_optimized_detector.onnx, optimized_detector.onnx, or model.onnx`); |
|
} else { |
|
showResult('error', `❌ Failed to initialize: ${error.message}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
async function tokenizeText(text, maxLength = 256) { |
|
try { |
|
|
|
const encoded = await tokenizer(text, { |
|
truncation: true, |
|
padding: 'max_length', |
|
max_length: maxLength, |
|
return_tensors: false |
|
}); |
|
|
|
console.log('Encoded result:', encoded); |
|
|
|
|
|
let inputIds, attentionMask; |
|
|
|
if (encoded.input_ids && Array.isArray(encoded.input_ids)) { |
|
|
|
inputIds = encoded.input_ids; |
|
attentionMask = encoded.attention_mask; |
|
} else if (encoded.input_ids && encoded.input_ids.data) { |
|
|
|
inputIds = Array.from(encoded.input_ids.data); |
|
attentionMask = Array.from(encoded.attention_mask.data); |
|
} else if (Array.isArray(encoded)) { |
|
|
|
inputIds = encoded; |
|
attentionMask = encoded.map(token => token === tokenizer.pad_token_id ? 0 : 1); |
|
} else { |
|
throw new Error('Unexpected tokenizer output format'); |
|
} |
|
|
|
|
|
if (inputIds.length !== maxLength) { |
|
console.warn(`Expected length ${maxLength}, got ${inputIds.length}`); |
|
|
|
if (inputIds.length < maxLength) { |
|
const padToken = tokenizer.pad_token_id || 0; |
|
while (inputIds.length < maxLength) { |
|
inputIds.push(padToken); |
|
attentionMask.push(0); |
|
} |
|
} else { |
|
inputIds = inputIds.slice(0, maxLength); |
|
attentionMask = attentionMask.slice(0, maxLength); |
|
} |
|
} |
|
|
|
return { |
|
input_ids: inputIds, |
|
attention_mask: attentionMask |
|
}; |
|
} catch (error) { |
|
console.error('Tokenization error:', error); |
|
throw new Error(`Failed to tokenize text: ${error.message}`); |
|
} |
|
} |
|
|
|
async function analyzeText() { |
|
const text = document.getElementById('textInput').value.trim(); |
|
|
|
if (!text) { |
|
showResult('error', 'Please enter some text to analyze.'); |
|
return; |
|
} |
|
|
|
if (text.length < 100) { |
|
showResult('error', 'Please enter at least 100 characters for accurate analysis.'); |
|
return; |
|
} |
|
|
|
if (!session || !tokenizer) { |
|
showResult('error', 'Model or tokenizer not loaded yet. Please wait...'); |
|
return; |
|
} |
|
|
|
|
|
setLoading(true); |
|
showResult('loading', 'Tokenizing and analyzing text...'); |
|
|
|
try { |
|
|
|
console.log('Tokenizing text...'); |
|
const tokenized = await tokenizeText(text, 256); |
|
|
|
console.log('Input IDs length:', tokenized.input_ids.length); |
|
console.log('Attention mask length:', tokenized.attention_mask.length); |
|
console.log('Sample tokens:', tokenized.input_ids.slice(0, 10)); |
|
console.log('Sample attention:', tokenized.attention_mask.slice(0, 10)); |
|
|
|
|
|
if (!tokenized.input_ids || !Array.isArray(tokenized.input_ids)) { |
|
throw new Error('Invalid tokenization: input_ids is not an array'); |
|
} |
|
|
|
if (!tokenized.attention_mask || !Array.isArray(tokenized.attention_mask)) { |
|
throw new Error('Invalid tokenization: attention_mask is not an array'); |
|
} |
|
|
|
if (tokenized.input_ids.length !== 256 || tokenized.attention_mask.length !== 256) { |
|
throw new Error(`Invalid tokenization: expected length 256, got input_ids: ${tokenized.input_ids.length}, attention_mask: ${tokenized.attention_mask.length}`); |
|
} |
|
|
|
|
|
const inputIds = new BigInt64Array(tokenized.input_ids.map(id => BigInt(id))); |
|
const attentionMask = new BigInt64Array(tokenized.attention_mask.map(mask => BigInt(mask))); |
|
|
|
|
|
const feeds = { |
|
'input_ids': new ort.Tensor('int64', inputIds, [1, 256]), |
|
'attention_mask': new ort.Tensor('int64', attentionMask, [1, 256]) |
|
}; |
|
|
|
console.log('Running inference...'); |
|
updateStatus('processing', '🧠 Running neural network inference...'); |
|
|
|
|
|
const startTime = performance.now(); |
|
const results = await session.run(feeds); |
|
const inferenceTime = performance.now() - startTime; |
|
|
|
console.log('Inference completed in', inferenceTime.toFixed(2), 'ms'); |
|
console.log('Raw output:', results.probability_human.data[0]); |
|
|
|
const probability = results.probability_human.data[0]; |
|
|
|
|
|
const isHuman = probability < 0.5; |
|
const confidence = Math.abs(probability - 0.5) * 2; |
|
|
|
|
|
const humanProbability = 1 - probability; |
|
|
|
updateStatus('complete', `✅ Analysis complete (${inferenceTime.toFixed(0)}ms)`); |
|
displayResults(humanProbability, isHuman, confidence, text.length, inferenceTime); |
|
|
|
} catch (error) { |
|
console.error('Analysis error:', error); |
|
updateStatus('error', `❌ Analysis failed: ${error.message}`); |
|
showResult('error', `Error analyzing text: ${error.message}`); |
|
} finally { |
|
setLoading(false); |
|
} |
|
} |
|
|
|
function displayResults(probability, isHuman, confidence, textLength, inferenceTime) { |
|
const resultDiv = document.getElementById('result'); |
|
const className = isHuman ? 'human' : 'ai'; |
|
const prediction = isHuman ? 'Human Written' : 'AI Generated'; |
|
const icon = isHuman ? '👤' : '🤖'; |
|
|
|
|
|
const estimatedTokens = Math.ceil(textLength / 4); |
|
|
|
resultDiv.className = `result ${className}`; |
|
resultDiv.style.display = 'block'; |
|
|
|
resultDiv.innerHTML = ` |
|
<div class="prediction">${icon} ${prediction}</div> |
|
<div class="confidence">Confidence: ${(confidence * 100).toFixed(1)}%</div> |
|
<div class="probability">Human Probability: ${(probability * 100).toFixed(1)}%</div> |
|
|
|
<div class="stats"> |
|
<div class="stat"> |
|
<span class="stat-value">${textLength}</span> |
|
<span class="stat-label">Characters</span> |
|
</div> |
|
<div class="stat"> |
|
<span class="stat-value">${estimatedTokens}</span> |
|
<span class="stat-label">Est. Tokens</span> |
|
</div> |
|
<div class="stat"> |
|
<span class="stat-value">${inferenceTime.toFixed(0)}ms</span> |
|
<span class="stat-label">Inference Time</span> |
|
</div> |
|
<div class="stat"> |
|
<span class="stat-value">${(probability * 100).toFixed(0)}%</span> |
|
<span class="stat-label">Human Score</span> |
|
</div> |
|
</div> |
|
|
|
<div style="margin-top: 15px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 10px; font-size: 0.9em;"> |
|
<strong>Performance:</strong> ${inferenceTime.toFixed(0)}ms inference time |
|
</div> |
|
`; |
|
|
|
|
|
resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
|
} |
|
|
|
function showResult(type, message) { |
|
const resultDiv = document.getElementById('result'); |
|
resultDiv.className = `result ${type}`; |
|
resultDiv.style.display = 'block'; |
|
|
|
if (type === 'loading') { |
|
resultDiv.innerHTML = ` |
|
<div style="display: flex; align-items: center; justify-content: center; gap: 10px;"> |
|
<div class="loading-spinner"></div> |
|
${message} |
|
</div> |
|
`; |
|
} else { |
|
resultDiv.innerHTML = `<div>${message}</div>`; |
|
} |
|
} |
|
|
|
function setLoading(isLoading) { |
|
const btn = document.getElementById('analyzeBtn'); |
|
const btnText = document.getElementById('btnText'); |
|
const btnSpinner = document.getElementById('btnSpinner'); |
|
|
|
btn.disabled = isLoading; |
|
btnText.style.display = isLoading ? 'none' : 'inline'; |
|
btnSpinner.style.display = isLoading ? 'inline-block' : 'none'; |
|
} |
|
|
|
function updateStatus(type, message) { |
|
const statusDiv = document.getElementById('status'); |
|
if (statusDiv) { |
|
statusDiv.textContent = message; |
|
statusDiv.className = `status ${type}`; |
|
} |
|
} |
|
|
|
function loadExample(type) { |
|
const textarea = document.getElementById('textInput'); |
|
|
|
if (type === 'human') { |
|
textarea.value = "I've been thinking a lot about creativity lately, especially after visiting the local art museum last weekend. There's something deeply moving about standing in front of a painting that someone poured their heart into decades or even centuries ago. The way light hits the canvas, the subtle imperfections in the brushstrokes, the stories hidden in every corner of the composition. It makes me wonder about the artist's life, their struggles, their moments of doubt and breakthrough. Art has this incredible power to transcend time and connect us with people we'll never meet, yet somehow understand on a profound level."; |
|
} else { |
|
textarea.value = "Here are the key steps to improve your writing skills: 1) Read extensively across different genres and styles to expand your vocabulary and understanding of various writing techniques. 2) Practice writing regularly, setting aside dedicated time each day for writing exercises or projects. 3) Seek feedback from peers, mentors, or writing groups to identify areas for improvement. 4) Study grammar and style guides to ensure technical accuracy. 5) Revise and edit your work multiple times, focusing on clarity, coherence, and flow. 6) Experiment with different writing formats and styles to find your unique voice. Following these steps consistently will help you develop stronger writing abilities over time."; |
|
} |
|
|
|
|
|
textarea.focus(); |
|
} |
|
|
|
|
|
document.getElementById('textInput').addEventListener('keydown', function(e) { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
analyzeText(); |
|
} |
|
}); |
|
|
|
|
|
window.analyzeText = analyzeText; |
|
|
|
|
|
window.addEventListener('load', initializeModel); |
|
</script> |
|
</body> |
|
</html> |