ai-text-humanizer / app /static /index.html
aikenml's picture
Update app/static/index.html
021d9fc verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AI Text Humanizer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
color: #1f2937;
overflow-y: auto; /* Allow page scrolling */
min-height: 100vh;
}
.container {
min-height: 100vh;
max-width: 1400px;
margin: 0 auto;
display: flex;
flex-direction: column;
padding: 16px;
}
/* ===== HEADER (Title + Subtitle) ===== */
.header {
text-align: center;
margin-bottom: 16px; /* Reduced bottom margin so header sits higher */
}
h1 {
font-size: 2.0em; /* Slightly smaller */
font-weight: 800;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 6px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
p.description {
color: rgba(255, 255, 255, 0.9);
font-size: 1.2em;
font-weight: 300;
}
/* ===== MAIN GRID (Input + Output Panels) ===== */
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
min-height: 70vh; /* Fixed height for the main panels */
margin-bottom: 24px;
}
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
gap: 20px;
}
h1 {
font-size: 1.8em;
}
}
@media (max-width: 768px) {
.container {
padding: 12px;
}
.panel {
padding: 16px;
}
h1 {
font-size: 1.6em;
}
p.description {
font-size: 1em;
}
}
/* ===== PANEL BASE STYLES ===== */
.panel {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
padding: 24px;
}
.input-panel {
background: rgba(255, 255, 255, 0.98);
}
.output-panel {
background: rgba(248, 250, 252, 0.98);
}
/* ===== PANEL HEADER (Title + Subtitle inside each panel) ===== */
.panel-header {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid rgba(99, 102, 241, 0.1);
}
.panel-title {
font-size: 1.3em;
font-weight: 600;
color: #374151;
margin-bottom: 6px;
}
.panel-subtitle {
color: #6b7280;
font-size: 0.9em;
}
/* ===== INPUT PANEL CONTROLS ===== */
textarea {
flex: 1; /* Takes all available vertical space */
font-size: 15px;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 12px;
resize: none;
transition: all 0.3s ease;
font-family: inherit;
line-height: 1.6;
background: #fafafa;
overflow-y: auto;
}
textarea:focus {
outline: none;
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
background: white;
}
textarea::placeholder {
color: #9ca3af;
}
/* ===== BUTTON STYLES ===== */
button {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 8px 16px; /* Further reduced */
font-size: 13px; /* Further reduced */
font-weight: 600;
border: none;
border-radius: 8px; /* Smaller radius */
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
max-width: 200px; /* Limit button width */
}
button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
}
button:active {
transform: translateY(0);
}
.copy-button {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
margin-top: 0;
flex-shrink: 0;
max-width: 140px; /* Same as file input button */
}
.copy-button:hover {
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
.copy-button.copied {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
}
.detector-button {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
margin-top: 0; /* Remove top margin */
flex-shrink: 0;
max-width: 180px; /* Slightly wider for longer text */
}
.detector-button:hover {
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
}
/* ===== FILE UPLOAD STYLES ===== */
.file-upload-area {
margin-top: 12px;
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
}
.buttons-row {
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.file-input-wrapper {
position: relative;
}
.file-input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.file-input-label {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 13px;
font-weight: 600;
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3);
min-height: 32px;
max-width: 140px; /* Limit width */
}
.file-input-label:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
}
.clear-button {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
padding: 8px 16px;
max-width: 100px; /* Limit width */
}
.clear-button:hover {
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.file-info {
font-size: 12px;
color: #6b7280;
margin-top: 8px;
padding: 0 4px;
text-align: center;
}
/* ===== OUTPUT CONTENT CONTAINER ===== */
.output-container {
display: flex;
flex-direction: column;
height: 400px; /* Fixed height for the container */
overflow: hidden; /* Prevent container from expanding */
}
/* ===== OUTPUT CONTENT (scrollable) ===== */
.output-content {
flex: 1; /* Fill vertical space between header and bottom button */
padding: 16px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 15px;
line-height: 1.7;
white-space: pre-wrap;
overflow-y: auto; /* Only this area scrolls */
color: #334155;
height: 400px; /* Fixed height instead of min-height */
max-height: 400px; /* Prevent expansion */
}
/* ===== BOTTOM BUTTON CONTAINER (flexible height) ===== */
.panel-bottom {
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 8px;
flex-shrink: 0; /* Prevent buttons from shrinking */
}
/* ===== AI DETECTOR SECTION STYLES ===== */
.detector-section {
display: none; /* Hidden by default */
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 24px;
margin-bottom: 24px;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.detector-results {
padding: 16px;
background: #ffffff;
border: 2px solid #e2e8f0;
border-radius: 12px;
}
.detector-table-title {
font-size: 1.1em;
font-weight: 600;
color: #374151;
margin-bottom: 12px;
text-align: center;
}
.detector-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.detector-table th {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 12px 8px;
text-align: left;
font-weight: 600;
border-radius: 8px 8px 0 0;
}
.detector-table th:first-child {
border-radius: 8px 0 0 0;
}
.detector-table th:last-child {
border-radius: 0 8px 0 0;
}
.detector-table td {
padding: 10px 8px;
border-bottom: 1px solid #e5e7eb;
}
.detector-table tr:nth-child(even) {
background-color: #f8fafc;
}
.detector-table tr:hover {
background-color: #f1f5f9;
}
.ai-percentage {
font-weight: 600;
color: #ef4444;
}
.ai-percentage.low {
color: #10b981;
}
.ai-percentage.medium {
color: #f59e0b;
}
.ai-percentage.high {
color: #ef4444;
}
/* ===== LOADING & ERROR STATES ===== */
.loading {
color: #64748b;
font-style: italic;
text-align: center;
padding: 40px 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.loading::before {
content: "✨";
animation: pulse 1.5s infinite;
}
.detector-loading {
color: #64748b;
font-style: italic;
text-align: center;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.detector-loading::before {
content: "πŸ”";
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.error {
color: #ef4444;
font-weight: 500;
text-align: center;
padding: 24px 16px;
background: rgba(239, 68, 68, 0.05);
border-radius: 8px;
}
/* ===== CUSTOM SCROLLBAR for output ===== */
.output-content::-webkit-scrollbar {
width: 8px;
}
.output-content::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 4px;
}
.output-content::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
.output-content::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* ===== DEBUG STYLES (temporary - remove in production) ===== */
.debug-info {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
z-index: 1000;
display: none; /* Hidden by default */
}
</style>
</head>
<body>
<div class="container">
<!-- Debug info (remove in production) -->
<div id="debugInfo" class="debug-info"></div>
<!-- ===== HEADER ===== -->
<div class="header">
<h1>🌟 AI Text Humanizer</h1>
<p class="description">Transform AI text into natural, human-like language</p>
</div>
<!-- ===== MAIN PANELS ===== -->
<div class="main-content">
<!-- ==================== INPUT PANEL ==================== -->
<div class="panel input-panel">
<div class="panel-header">
<div class="panel-title">πŸ“ Input Text</div>
<div class="panel-subtitle">Paste your AI-generated content or upload a file</div>
</div>
<textarea
id="inputText"
placeholder="Paste your AI-generated text here and watch the magic happen..."
></textarea>
<div class="panel-bottom">
<!-- All buttons in one row -->
<div class="buttons-row">
<button type="button" onclick="humanize()">πŸš€ Humanize Text</button>
<div class="file-input-wrapper">
<input type="file" id="fileInput" class="file-input" accept=".txt,.doc,.docx,.pdf" onchange="handleFileUpload(event)">
<label for="fileInput" class="file-input-label">
πŸ“ Choose File
</label>
</div>
<button type="button" class="clear-button" onclick="clearText()">πŸ—‘οΈ Clear</button>
</div>
<div id="fileInfo" class="file-info" style="display: none;"></div>
</div>
</div>
<!-- ==================== OUTPUT PANEL ==================== -->
<div class="panel output-panel">
<div class="panel-header">
<div class="panel-title">✨ Humanized Result</div>
<div class="panel-subtitle">Your transformed, natural text</div>
</div>
<div class="output-container">
<!-- #result holds loading / error / final text; scrollable -->
<div id="result" class="output-content">
<div class="loading">Your humanized text will appear here...</div>
</div>
</div>
<div class="panel-bottom">
<!-- Copy and Detector buttons in one row -->
<div class="buttons-row">
<button
id="copyBtn"
type="button"
class="copy-button"
style="display: none;"
onclick="copyText()"
>
πŸ“‹ Copy Text
</button>
<button
id="detectorBtn"
type="button"
class="detector-button"
style="display: none;"
onclick="runDetector()"
>
πŸ” AI Detector Results
</button>
</div>
</div>
</div>
</div>
<!-- ===== AI DETECTOR SECTION (appears at bottom) ===== -->
<div id="detectorSection" class="detector-section">
<div class="panel-header">
<div class="panel-title">πŸ” AI Detector Analysis</div>
<div class="panel-subtitle">Detailed analysis of your humanized text</div>
</div>
<div id="detectorResults"></div>
</div>
</div>
<!-- ===== JAVASCRIPT ===== -->
<script>
let currentHumanizedText = '';
let debugMode = false; // Set to true for debugging
// Debug function
function updateDebugInfo(message) {
if (!debugMode) return;
const debugEl = document.getElementById('debugInfo');
debugEl.textContent = message;
debugEl.style.display = 'block';
console.log('DEBUG:', message);
}
// Toggle debug mode with Ctrl+D
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'd') {
e.preventDefault();
debugMode = !debugMode;
const debugEl = document.getElementById('debugInfo');
debugEl.style.display = debugMode ? 'block' : 'none';
updateDebugInfo('Debug mode: ' + (debugMode ? 'ON' : 'OFF'));
}
});
// File upload handler
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
updateDebugInfo('File selected: ' + file.name);
const fileInfo = document.getElementById('fileInfo');
const textArea = document.getElementById('inputText');
// Show file info
fileInfo.textContent = `πŸ“„ ${file.name} (${(file.size / 1024).toFixed(1)} KB)`;
fileInfo.style.display = 'block';
try {
let text = '';
if (file.type === 'text/plain' || file.name.endsWith('.txt')) {
// Handle plain text files
text = await file.text();
} else if (file.name.endsWith('.docx')) {
// For DOCX files, show a message since mammoth requires external library
textArea.value = '⚠️ DOCX files are not yet supported. Please copy and paste the text manually or use a .txt file.';
fileInfo.textContent += ' - DOCX support coming soon!';
return;
} else if (file.name.endsWith('.pdf')) {
// For PDF files, show a message since PDF parsing is complex
textArea.value = '⚠️ PDF files are not yet supported. Please copy and paste the text manually or use a .txt file.';
fileInfo.textContent += ' - PDF support coming soon!';
return;
} else {
// Try to read as text for other formats
text = await file.text();
}
// Set the text in the textarea
textArea.value = text;
// Trigger the auto-resize
textArea.style.height = 'auto';
textArea.style.height = textArea.scrollHeight + 'px';
updateDebugInfo('File loaded successfully, text length: ' + text.length);
fileInfo.textContent += ' - βœ… Loaded successfully!';
} catch (error) {
updateDebugInfo('Error reading file: ' + error.message);
fileInfo.textContent = `❌ Error reading ${file.name}: ${error.message}`;
fileInfo.style.color = '#ef4444';
console.error('File reading error:', error);
}
}
// Clear text and file
function clearText() {
const textArea = document.getElementById('inputText');
const fileInput = document.getElementById('fileInput');
const fileInfo = document.getElementById('fileInfo');
const outputDiv = document.getElementById('result');
const copyBtn = document.getElementById('copyBtn');
const detectorBtn = document.getElementById('detectorBtn');
const detectorSection = document.getElementById('detectorSection');
// Clear all inputs and outputs
textArea.value = '';
fileInput.value = '';
fileInfo.style.display = 'none';
fileInfo.style.color = '#6b7280'; // Reset color
// Reset output area
outputDiv.innerHTML = '<div class="loading">Your humanized text will appear here...</div>';
copyBtn.style.display = 'none';
detectorBtn.style.display = 'none';
detectorSection.style.display = 'none';
// Reset stored text
currentHumanizedText = '';
// Reset textarea height
textArea.style.height = 'auto';
updateDebugInfo('All content cleared');
}
async function humanize() {
const textArea = document.getElementById("inputText");
const outputDiv = document.getElementById("result");
const copyBtn = document.getElementById("copyBtn");
const detectorBtn = document.getElementById("detectorBtn");
const detectorSection = document.getElementById("detectorSection");
const text = textArea.value.trim();
if (!text) {
alert("Please enter some text first!");
return;
}
updateDebugInfo('Starting humanize process...');
// Hide detector section when starting new humanization
detectorSection.style.display = "none";
// Clear previous results completely
outputDiv.innerHTML = "";
// Show loading spinner
const spinner = document.createElement("div");
spinner.className = "loading";
spinner.textContent = "Processing your text...";
outputDiv.appendChild(spinner);
// Hide buttons until we have a successful response
copyBtn.style.display = "none";
detectorBtn.style.display = "none";
try {
updateDebugInfo('Sending request to /humanize...');
const response = await fetch("/humanize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
});
updateDebugInfo(`Response status: ${response.status}`);
if (!response.ok) {
throw new Error(`Server responded with status ${response.status}`);
}
const data = await response.json();
updateDebugInfo('Received humanized text, length: ' + data.humanized.length);
// Store the humanized text for detector
currentHumanizedText = data.humanized;
// Replace spinner with the actual humanized text
outputDiv.innerHTML = "";
const textNode = document.createTextNode(data.humanized);
outputDiv.appendChild(textNode);
// Show buttons
copyBtn.style.display = "block";
detectorBtn.style.display = "block";
updateDebugInfo('Humanize completed successfully');
} catch (error) {
updateDebugInfo('Error in humanize: ' + error.message);
outputDiv.innerHTML = "";
const errDiv = document.createElement("div");
errDiv.className = "error";
errDiv.textContent = `❌ Error: ${error.message}`;
outputDiv.appendChild(errDiv);
console.error("Error:", error);
}
}
async function runDetector() {
if (!currentHumanizedText) {
alert("No humanized text available for detection!");
return;
}
updateDebugInfo('Starting detector process...');
const detectorSection = document.getElementById("detectorSection");
const detectorResults = document.getElementById("detectorResults");
const detectorBtn = document.getElementById("detectorBtn");
// Show the detector section and scroll to it
detectorSection.style.display = "block";
setTimeout(() => {
detectorSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
// Show loading state in the detector results area
detectorResults.innerHTML = "";
const spinner = document.createElement("div");
spinner.className = "detector-loading";
spinner.textContent = "Running AI detection...";
detectorResults.appendChild(spinner);
// Disable button during processing
detectorBtn.disabled = true;
detectorBtn.textContent = "πŸ” Analyzing...";
try {
updateDebugInfo('Sending request to /detect...');
const response = await fetch("/detect", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text: currentHumanizedText }),
});
updateDebugInfo(`Detect response status: ${response.status}`);
if (!response.ok) {
throw new Error(`Server responded with status ${response.status}`);
}
const data = await response.json();
updateDebugInfo('Received detector results, count: ' + data.results.length);
// Create the detector results table
detectorResults.innerHTML = `
<div class="detector-results">
<div class="detector-table-title">πŸ” AI Detector Tool Results</div>
<table class="detector-table">
<thead>
<tr>
<th>AI Detector Tool</th>
<th>AI Probability</th>
</tr>
</thead>
<tbody>
${data.results.map(result => {
const label = result.ai_probability > 0.9 ? "AI-generated" : "Human-written";
return `
<tr>
<td>${result.detector_name}</td>
<td class="ai-label">${label}</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
updateDebugInfo('Detector results displayed successfully');
} catch (error) {
updateDebugInfo('Error in detector: ' + error.message);
// Add error message
detectorResults.innerHTML = "";
const errDiv = document.createElement("div");
errDiv.className = "error";
errDiv.textContent = `❌ Detection Error: ${error.message}`;
detectorResults.appendChild(errDiv);
console.error("Detection Error:", error);
} finally {
// Re-enable button
detectorBtn.disabled = false;
detectorBtn.textContent = "πŸ” AI Detector Results";
}
}
async function copyText() {
const copyBtn = document.getElementById("copyBtn");
const textToCopy = currentHumanizedText;
if (!textToCopy) {
alert("No text to copy!");
return;
}
try {
await navigator.clipboard.writeText(textToCopy);
// Instant feedback on the button
const original = copyBtn.innerHTML;
copyBtn.innerHTML = "βœ… Copied!";
copyBtn.classList.add("copied");
setTimeout(() => {
copyBtn.innerHTML = original;
copyBtn.classList.remove("copied");
}, 2000);
} catch {
// Fallback for older browsers
const tmp = document.createElement("textarea");
tmp.value = textToCopy;
document.body.appendChild(tmp);
tmp.select();
document.execCommand("copy");
document.body.removeChild(tmp);
copyBtn.innerHTML = "βœ… Copied!";
setTimeout(() => {
copyBtn.innerHTML = "πŸ“‹ Copy Text";
}, 2000);
}
}
// Ctrl+Enter = humanize
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.key === "Enter") {
humanize();
}
});
// Auto-resize the input textarea as you type
document.getElementById("inputText").addEventListener("input", function () {
this.style.height = "auto";
this.style.height = this.scrollHeight + "px";
});
</script>
</body>
</html>