Joshfcooper's picture
Upload AI text detector model
e7678d5 verified
<!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;
// Initialize ONNX Runtime and load model + tokenizer
async function initializeModel() {
try {
console.log('Loading tokenizer and ONNX model...');
// Load the actual tokenizer from HuggingFace Hub
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...');
// Load ONNX model - try multiple possible filenames
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) {
// Only log if it's not a 404 error to reduce console spam
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);
// Enable the analyze button
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}`);
// Show helpful error message based on the type of error
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}`);
}
}
}
// Tokenize text using the proper tokenizer
async function tokenizeText(text, maxLength = 256) {
try {
// Use the actual tokenizer with proper settings
const encoded = await tokenizer(text, {
truncation: true,
padding: 'max_length',
max_length: maxLength,
return_tensors: false // We'll handle tensor creation manually
});
console.log('Encoded result:', encoded);
// Handle different possible return formats
let inputIds, attentionMask;
if (encoded.input_ids && Array.isArray(encoded.input_ids)) {
// Direct array format
inputIds = encoded.input_ids;
attentionMask = encoded.attention_mask;
} else if (encoded.input_ids && encoded.input_ids.data) {
// Tensor-like format
inputIds = Array.from(encoded.input_ids.data);
attentionMask = Array.from(encoded.attention_mask.data);
} else if (Array.isArray(encoded)) {
// Sometimes returns just the token IDs
inputIds = encoded;
attentionMask = encoded.map(token => token === tokenizer.pad_token_id ? 0 : 1);
} else {
throw new Error('Unexpected tokenizer output format');
}
// Ensure we have the right length
if (inputIds.length !== maxLength) {
console.warn(`Expected length ${maxLength}, got ${inputIds.length}`);
// Pad or truncate as needed
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;
}
// Show loading state
setLoading(true);
showResult('loading', 'Tokenizing and analyzing text...');
try {
// Tokenize the text using the proper tokenizer
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));
// Validate tokenization
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}`);
}
// Convert to the correct format for ONNX
const inputIds = new BigInt64Array(tokenized.input_ids.map(id => BigInt(id)));
const attentionMask = new BigInt64Array(tokenized.attention_mask.map(mask => BigInt(mask)));
// Create ONNX tensors with correct shapes
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...');
// Run 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];
// Interpret results - flip the logic since it seems backwards
const isHuman = probability < 0.5; // Changed from > to <
const confidence = Math.abs(probability - 0.5) * 2;
// Display the corrected probability (1 - probability for human score)
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 ? '👤' : '🤖';
// Calculate token count (approximate)
const estimatedTokens = Math.ceil(textLength / 4); // Rough estimate
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>
`;
// Scroll to results
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.";
}
// Auto-focus the textarea
textarea.focus();
}
// Handle Enter key in textarea (Shift+Enter for new line, Enter to analyze)
document.getElementById('textInput').addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
analyzeText();
}
});
// Make functions globally available
window.analyzeText = analyzeText;
// Initialize the model when page loads
window.addEventListener('load', initializeModel);
</script>
</body>
</html>