satyaki-mitra's picture
UI Gauge fixed
154e93c
<!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 - Verifying Content Authenticity Using Statistics</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #06b6d4;
--primary-dark: #0891b2;
--secondary: #3b82f6;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--bg-dark: #0f172a;
--bg-darker: #020617;
--bg-panel: rgba(30, 41, 59, 0.95);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border: rgba(71, 85, 105, 0.5);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}
/* Header */
.header {
background: rgba(15, 23, 42, 0.98);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 1.5rem;
font-weight: 700;
color: #fff;
text-decoration: none;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
}
.nav-links {
display: flex;
gap: 2rem;
align-items: center;
}
.nav-link {
color: var(--text-secondary);
text-decoration: none;
font-weight: 500;
transition: color 0.3s;
cursor: pointer;
}
.nav-link:hover {
color: var(--primary);
}
.try-btn {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: #fff;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 600;
border: none;
cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
text-decoration: none;
display: inline-block;
}
.try-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(6, 182, 212, 0.4);
}
/* Landing Page */
.landing-page {
display: block;
}
.hero {
max-width: 1200px;
margin: 0 auto;
padding: 6rem 2rem 4rem;
text-align: center;
}
.hero-title {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #fff 0%, var(--primary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1.2;
}
.hero-subtitle {
font-size: 1.5rem;
color: var(--text-secondary);
margin-bottom: 1rem;
}
.hero-description {
font-size: 1.1rem;
color: var(--text-muted);
max-width: 700px;
margin: 0 auto 3rem;
}
.accuracy-badge {
display: inline-block;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(6, 182, 212, 0.2) 100%);
border: 2px solid var(--success);
padding: 1rem 2rem;
border-radius: 12px;
font-size: 1.5rem;
font-weight: 700;
color: var(--success);
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
max-width: 1000px;
margin: 4rem auto;
padding: 0 2rem;
}
.stat-card {
background: var(--bg-panel);
padding: 2rem;
border-radius: 16px;
border: 1px solid var(--border);
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 0.5rem;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.95rem;
}
/* Features Section */
.features-section {
max-width: 1200px;
margin: 6rem auto;
padding: 0 2rem;
}
.section-title {
font-size: 2.5rem;
font-weight: 700;
text-align: center;
margin-bottom: 1rem;
}
.section-subtitle {
text-align: center;
color: var(--text-secondary);
font-size: 1.1rem;
margin-bottom: 4rem;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2rem;
}
.feature-card {
background: var(--bg-panel);
padding: 2.5rem;
border-radius: 16px;
border: 1px solid var(--border);
transition: transform 0.3s, box-shadow 0.3s;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(6, 182, 212, 0.2);
}
.feature-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.feature-title {
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 1rem;
color: #fff;
}
.feature-description {
color: var(--text-secondary);
line-height: 1.6;
}
/* Metrics Section */
.metrics-info {
max-width: 1200px;
margin: 6rem auto;
padding: 0 2rem;
}
.metric-card {
background: var(--bg-panel);
padding: 2rem;
border-radius: 12px;
border: 1px solid var(--border);
margin-bottom: 1.5rem;
display: grid;
grid-template-columns: 100px 1fr;
gap: 2rem;
align-items: center;
}
.metric-icon-box {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.metric-content h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
color: #fff;
}
.metric-weight {
display: inline-block;
background: rgba(6, 182, 212, 0.2);
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--primary);
font-weight: 600;
margin-left: 0.5rem;
}
/* Analysis Interface */
.analysis-interface {
display: none;
max-width: 1600px;
margin: 2rem auto;
padding: 0 2rem 2rem;
}
.interface-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: start;
}
.panel {
background: var(--bg-panel);
border-radius: 16px;
padding: 2rem;
border: 1px solid var(--border);
backdrop-filter: blur(10px);
height: 850px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.panel-content {
flex: 1;
overflow-y: auto;
padding: 1rem 0;
}
.panel-title {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: #fff;
}
.input-tabs {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.input-tab {
flex: 1;
padding: 0.75rem 1rem;
background: rgba(51, 65, 85, 0.6);
border: none;
border-radius: 8px;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
transition: all 0.3s;
}
.input-tab.active {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: #fff;
}
.input-tab:hover:not(.active) {
background: rgba(71, 85, 105, 0.8);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.text-input {
width: 100%;
min-height: 450px;
padding: 1rem;
background: rgba(15, 23, 42, 0.8);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.95rem;
line-height: 1.8;
resize: vertical;
font-family: inherit;
}
.text-input::placeholder {
color: var(--text-muted);
}
.text-input:focus {
outline: none;
border-color: var(--primary);
}
.file-upload-area {
border: 2px dashed var(--border);
border-radius: 8px;
padding: 3rem;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: rgba(15, 23, 42, 0.5);
}
.file-upload-area:hover {
border-color: var(--primary);
background: rgba(6, 182, 212, 0.05);
}
.file-upload-area.drag-over {
border-color: var(--primary);
background: rgba(6, 182, 212, 0.1);
}
.file-upload-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.file-input {
display: none;
}
.file-name-display {
margin-top: 1rem;
padding: 0.75rem;
background: rgba(6, 182, 212, 0.1);
border-radius: 6px;
color: var(--primary);
display: none;
}
.options-section {
margin: 1.5rem 0;
padding: 1rem;
background: rgba(51, 65, 85, 0.3);
border-radius: 8px;
}
.option-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.option-row:last-child {
margin-bottom: 0;
}
.option-label {
font-size: 0.9rem;
color: var(--text-secondary);
flex: 1;
}
select {
background: rgba(15, 23, 42, 0.8);
border: 1px solid var(--border);
padding: 0.5rem;
border-radius: 6px;
color: var(--text-primary);
font-size: 0.9rem;
cursor: pointer;
}
select:focus {
outline: none;
border-color: var(--primary);
}
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.analyze-btn {
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
}
.analyze-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(6, 182, 212, 0.3);
}
.analyze-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Report Tabs */
.report-tabs {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
padding-bottom: 0.5rem;
}
.report-tab {
padding: 0.75rem 1rem;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
border-bottom: 3px solid transparent;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.report-tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
.report-content {
display: none;
}
.report-content.active {
display: block;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
}
.empty-icon {
width: 80px;
height: 80px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
}
.empty-title {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1rem;
color: #fff;
}
.empty-description {
color: var(--text-secondary);
line-height: 1.6;
}
/* Loading State */
.loading {
text-align: center;
padding: 3rem;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(71, 85, 105, 0.3);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Result Summary */
.result-summary {
text-align: center;
padding: 2rem 0;
}
.gauge-container {
width: 220px;
height: 220px;
margin: 0 auto 2rem;
position: relative;
}
.gauge-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(var(--gauge-color) 0deg, var(--gauge-color) var(--gauge-degree), rgba(51, 65, 85, 0.3) var(--gauge-degree));
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.gauge-inner {
width: 170px;
height: 170px;
background: var(--bg-panel);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gauge-value {
font-size: 3rem;
font-weight: 800;
color: var(--gauge-color);
}
.gauge-label {
font-size: 0.9rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
.result-info-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1.5rem;
margin: 2rem 0;
}
.info-card {
background: rgba(51, 65, 85, 0.3);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--border);
}
.info-label {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
font-size: 1.4rem;
font-weight: 700;
color: #fff;
}
.confidence-badge {
display: inline-block;
padding: 0.4rem 1rem;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 600;
}
.confidence-high {
background: rgba(16, 185, 129, 0.2);
color: var(--success);
}
.confidence-medium {
background: rgba(245, 158, 11, 0.2);
color: var(--warning);
}
.confidence-low {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
/* Reasoning Box */
.reasoning-box {
background: rgba(51, 65, 85, 0.4);
padding: 1.5rem;
border-radius: 10px;
border-left: 4px solid var(--primary);
margin-top: 2rem;
}
.reasoning-title {
font-weight: 700;
margin-bottom: 1rem;
color: var(--primary);
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.reasoning-text {
color: var(--text-secondary);
line-height: 1.7;
}
/* Reasoning Styles */
.reasoning-box.enhanced {
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
border: 1px solid rgba(71, 85, 105, 0.5);
border-radius: 12px;
padding: 1.5rem;
margin-top: 2rem;
backdrop-filter: blur(10px);
}
.reasoning-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
}
.reasoning-icon {
font-size: 1.5rem;
}
.reasoning-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--primary);
flex: 1;
}
.confidence-tag {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
}
.high-confidence {
background: rgba(16, 185, 129, 0.2);
color: var(--success);
border: 1px solid rgba(16, 185, 129, 0.3);
}
.medium-confidence {
background: rgba(245, 158, 11, 0.2);
color: var(--warning);
border: 1px solid rgba(245, 158, 11, 0.3);
}
.low-confidence {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
border: 1px solid rgba(239, 68, 68, 0.3);
}
.verdict-summary {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding: 1rem;
background: rgba(51, 65, 85, 0.3);
border-radius: 8px;
}
.verdict-text {
font-size: 1.3rem;
font-weight: 800;
color: var(--warning);
}
.probability {
color: var(--text-secondary);
font-size: 0.95rem;
}
.probability-value {
color: var(--text-primary);
font-weight: 700;
}
.metrics-breakdown {
margin-bottom: 1.5rem;
}
.breakdown-header {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-indicator {
display: flex;
justify-content: space-between;
align-items: left;
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.metric-indicator:hover {
background: rgba(51, 65, 85, 0.4);
transform: translateX(4px);
}
.metric-name {
font-weight: 400;
color: var(--text-primary);
min-width: 140px;
}
.metric-details {
display: flex;
gap: 1rem;
align-items: center;
}
.verdict-badge {
padding: 0.2rem 0.6rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
min-width: 60px;
text-align: center;
}
.ai-badge {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
border: 1px solid rgba(239, 68, 68, 0.3);
}
.human-badge {
background: rgba(16, 185, 129, 0.2);
color: var(--success);
border: 1px solid rgba(16, 185, 129, 0.3);
}
.confidence, .weight {
font-size: 0.8rem;
color: var(--text-muted);
min-width: 100px;
}
.agreement-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.2);
border-radius: 8px;
color: var(--success);
}
.agreement-icon {
font-weight: 700;
}
.agreement-text {
font-size: 0.9rem;
font-weight: 600;
}
/* Attribution Section */
.attribution-section {
margin-top: 2rem;
padding: 1.5rem;
background: rgba(51, 65, 85, 0.3);
border-radius: 10px;
border: 1px solid var(--border);
}
.attribution-title {
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 1rem;
color: #fff;
}
.model-match {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background: rgba(6, 182, 212, 0.1);
border-radius: 6px;
margin-bottom: 0.5rem;
}
.model-name {
font-weight: 600;
color: var(--text-primary);
}
.model-confidence {
font-weight: 700;
color: var(--primary);
}
.attribution-confidence {
margin-top: 0.75rem;
font-size: 0.85rem;
color: var(--text-secondary);
}
.attribution-uncertain {
color: var(--text-muted);
font-style: italic;
margin-top: 0.5rem;
font-size: 0.9rem;
}
.attribution-reasoning {
color: var(--text-secondary);
margin-top: 1rem;
font-size: 0.9rem;
line-height: 1.4;
}
/* Download Actions */
.download-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
}
.download-btn {
flex: 1;
padding: 0.75rem;
background: rgba(51, 65, 85, 0.6);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.download-btn:hover {
background: var(--primary);
border-color: var(--primary);
transform: translateY(-2px);
}
/* Action Buttons */
.action-buttons {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.action-btn {
flex: 1;
padding: 0.75rem;
background: rgba(51, 65, 85, 0.6);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.action-btn:hover {
background: var(--primary);
border-color: var(--primary);
transform: translateY(-2px);
}
.action-btn.refresh {
background: rgba(245, 158, 11, 0.2);
border-color: var(--warning);
color: var(--warning);
}
.action-btn.refresh:hover {
background: var(--warning);
color: var(--bg-darker);
}
/* Metrics Grid */
.metrics-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.metric-result-card {
background: rgba(51, 65, 85, 0.4);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--border);
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.metric-name {
font-weight: 700;
color: #fff;
font-size: 1.1rem;
}
.metric-score {
font-size: 1.8rem;
font-weight: 800;
}
.metric-verdict {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
margin-top: 0.5rem;
}
.verdict-ai {
background: rgba(239, 68, 68, 0.2);
color: var(--danger);
}
.verdict-human {
background: rgba(16, 185, 129, 0.2);
color: var(--success);
}
.verdict-uncertain {
background: rgba(245, 158, 11, 0.2);
color: var(--warning);
}
.metric-description {
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.5;
margin-top: 0.75rem;
}
/* Highlighted Text */
.highlight-legend {
display: flex;
gap: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: rgba(51, 65, 85, 0.4);
border-radius: 8px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.legend-color {
width: 20px;
height: 20px;
border-radius: 4px;
}
.legend-label {
font-size: 0.9rem;
color: var(--text-secondary);
}
.highlighted-text {
background: rgba(15, 23, 42, 0.8);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--border);
line-height: 1.9;
font-size: 0.95rem;
}
/* Footer */
.footer {
max-width: 1200px;
margin: 6rem auto 0;
padding: 3rem 2rem;
border-top: 1px solid var(--border);
text-align: center;
color: var(--text-muted);
}
/* Metrics Carousel */
.metrics-carousel-container {
display: flex;
flex-direction: column;
height: 100%;
}
.metrics-carousel-content {
flex: 1;
padding: 0;
display: flex;
align-items: flex-start;
justify-content: flex-start;
overflow-y: auto;
padding: 1rem;
}
.metric-slide {
display: none;
width: 100%;
padding: 1rem;
}
.metric-slide.active {
display: block;
}
.metrics-carousel-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-top: 1px solid var(--border);
background: rgba(15, 23, 42, 0.8);
}
.carousel-btn {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: #fff;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
}
.carousel-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(6, 182, 212, 0.4);
}
.carousel-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.carousel-position {
font-size: 0.9rem;
color: var(--text-secondary);
font-weight: 600;
}
/* Info Card Text Styles */
.verdict-text {
font-size: 1.2rem !important;
}
.domain-text {
font-size: 1.1rem !important;
}
.verdict-mixed {
background: rgba(168, 85, 247, 0.2);
color: #a855f7;
border: 1px solid rgba(168, 85, 247, 0.3);
}
/* Reasoning Bullet Points */
.reasoning-bullet-points {
margin: 1.5rem 0;
line-height: 1.6;
text-align: left;
}
.bullet-point {
margin-bottom: 0.75rem;
padding-left: 0.5rem;
color: var(--text-secondary);
font-size: 0.95rem;
text-align: left;
}
.bullet-point:last-child {
margin-bottom: 0;
}
.bullet-point strong {
color: var(--text-primary);
}
/* Responsive */
@media (max-width: 1200px) {
.interface-grid {
grid-template-columns: 1fr;
}
.metrics-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.features-grid {
grid-template-columns: 1fr;
}
.metric-card {
grid-template-columns: 1fr;
text-align: center;
}
.result-info-grid {
grid-template-columns: 1fr;
}
.nav-links {
display: none;
}
.download-actions,
.action-buttons {
flex-direction: column;
}
.highlight-legend {
flex-direction: column;
gap: 0.75rem;
}
.panel {
height: auto;
min-height: 600px;
}
}
/* Scroll Behavior */
html {
scroll-behavior: smooth;
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<a href="#" class="logo" onclick="showLanding(); return false;">
<div class="logo-icon">🔍</div>
<span>AI Text Detector</span>
</a>
<div class="nav-links">
<a href="#features" class="nav-link">Features</a>
<a href="#metrics" class="nav-link">Detection Metrics</a>
<a href="#" class="nav-link" onclick="showAnalysis(); return false;">Try It Now</a>
</div>
</div>
<!-- Landing Page -->
<div class="landing-page" id="landing-page">
<!-- Hero Section -->
<section class="hero">
<h1 class="hero-title">AI Text Detection Platform</h1>
<p class="hero-subtitle">Verifying Content Authenticity with Precision</p>
<p class="hero-description">
Production-ready platform designed to identify AI-generated content across education,
publishing, hiring, and research domains using sophisticated ensemble detection.
</p>
<button class="try-btn" onclick="showAnalysis()"> Try It Now → </button>
</section>
<!-- Stats -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">2.4%</div>
<div class="stat-label">False Positive Rate</div>
</div>
<div class="stat-card">
<div class="stat-value">6</div>
<div class="stat-label">Total Detection Metrics</div>
</div>
<div class="stat-card">
<div class="stat-value">5s</div>
<div class="stat-label">Average Processing Time</div>
</div>
</div>
<!-- Features Section -->
<section class="features-section" id="features">
<h2 class="section-title">Why Choose Our Platform?</h2>
<p class="section-subtitle">
Advanced technology meets practical application
</p>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🎯</div>
<h3 class="feature-title">Domain-Aware Detection</h3>
<p class="feature-description">
Calibrated thresholds for Academic, Technical, Creative, and Casual content types with specialized detection algorithms for each domain.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔬</div>
<h3 class="feature-title">6-Metric Ensemble</h3>
<p class="feature-description">
Combines Perplexity, Entropy, Statistical, Linguistic, Semantic Analysis, and Multi-Perturbation Stability for comprehensive detection with orthogonal signal capture.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">💡</div>
<h3 class="feature-title">Explainable Results</h3>
<p class="feature-description">
Sentence-level highlighting with confidence scores and detailed reasoning for every detection decision.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">🚀</div>
<h3 class="feature-title">Fast Processing</h3>
<p class="feature-description">
Analyze short texts in 1.2 seconds, medium documents in 3.5 seconds with parallel metric computation.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3 class="feature-title">Model Attribution</h3>
<p class="feature-description">
Identifies which AI model likely generated the text - GPT-4, Claude, Gemini, LLaMA, and more.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">📄</div>
<h3 class="feature-title">Multi-Format Support</h3>
<p class="feature-description">
Upload and analyze TXT, PDF, DOCX, DOC, and Markdown files with automatic text extraction.
</p>
</div>
</div>
</section>
<!-- Metrics Section -->
<section class="metrics-info" id="metrics">
<h2 class="section-title">Detection Metrics Explained</h2>
<p class="section-subtitle">
Understanding the science behind the detection
</p>
<div class="metric-card">
<div class="metric-icon-box">📊</div>
<div class="metric-content">
<h3>Perplexity <span class="metric-weight">Weight: 25%</span></h3>
<p>Measures how predictable the text is using GPT-2 language model. AI-generated text typically has lower perplexity (more predictable) than human writing, which tends to be more varied and surprising.</p>
</div>
</div>
<div class="metric-card">
<div class="metric-icon-box">🎲</div>
<div class="metric-content">
<h3>Entropy <span class="metric-weight">Weight: 20%</span></h3>
<p>Calculates token-level diversity and unpredictability in text sequences. Human writing shows higher entropy with more varied word choices, while AI tends toward more uniform token distributions.</p>
</div>
</div>
<div class="metric-card">
<div class="metric-icon-box">📈</div>
<div class="metric-content">
<h3>Structural Analysis <span class="metric-weight">Weight: 15%</span></h3>
<p>Analyzes sentence length variance, punctuation patterns, and lexical burstiness. Human writing exhibits more variation in sentence structure and rhythm compared to AI's consistent patterns.</p>
</div>
</div>
<div class="metric-card">
<div class="metric-icon-box">📝</div>
<div class="metric-content">
<h3>Linguistic Analysis <span class="metric-weight">Weight: 15%</span></h3>
<p>Evaluates POS tag diversity, syntactic complexity, and grammatical patterns. Examines the richness of language structures and whether they match natural human linguistic variation.</p>
</div>
</div>
<div class="metric-card">
<div class="metric-icon-box">🧠</div>
<div class="metric-content">
<h3>Semantic Analysis <span class="metric-weight">Weight: 15%</span></h3>
<p>Assesses semantic coherence, repetition patterns, and contextual consistency. Detects the subtle semantic fingerprints that distinguish AI-generated content from human writing.</p>
</div>
</div>
<div class="metric-card">
<div class="metric-icon-box">🔍</div>
<div class="metric-content">
<h3>Multi-Perturbation Stability <span class="metric-weight">Weight: 10%</span></h3>
<p>Tests text stability under random perturbations. AI-generated text tends to maintain higher likelihood scores even when slightly modified, while human text shows more variation.</p>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<p>&copy; 2025 AI Text Detector Platform</p>
<p style="margin-top: 1rem;">AI detection with enterprise accuracy and explainability.</p>
</footer>
</div>
<!-- Analysis Interface -->
<div class="analysis-interface" id="analysis-interface">
<div class="interface-grid">
<!-- Left Panel: Input -->
<div class="panel">
<h2 class="panel-title">Submit Content for Analysis</h2>
<div class="panel-content">
<div class="input-tabs">
<button class="input-tab active" data-tab="paste">
📋 Paste Text
</button>
<button class="input-tab" data-tab="upload">
📁 Upload File
</button>
</div>
<div id="paste-tab" class="tab-content active">
<textarea
id="text-input"
class="text-input"
placeholder="Paste your text here for analysis...
The more text you provide (minimum 50 characters), the more accurate the detection will be."
></textarea>
</div>
<div id="upload-tab" class="tab-content">
<div class="file-upload-area" id="file-upload-area">
<input type="file" id="file-input" class="file-input" accept=".txt,.pdf,.docx,.doc,.md">
<div class="file-upload-icon">📄</div>
<div style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;">
Click to upload or drag and drop
</div>
<div style="color: var(--text-muted); font-size: 0.9rem;">
Supported formats: TXT, PDF, DOCX, DOC, MD
</div>
<div style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
Maximum file size: 10MB
</div>
</div>
<div id="file-name-display" class="file-name-display"></div>
</div>
<div class="options-section">
<div class="option-row">
<label class="option-label">Content Domain:</label>
<select id="domain-select">
<option value="">Auto-detect</option>
<option value="academic">Academic</option>
<option value="technical_doc">Technical/Medical</option>
<option value="creative">Creative Writing</option>
<option value="social_media">Social Media</option>
</select>
</div>
<div class="option-row">
<label class="option-label">Enable AI Model Attribution:</label>
<div class="checkbox-wrapper">
<input type="checkbox" id="enable-attribution" checked>
<span style="font-size: 0.85rem; color: var(--text-muted);">Identify which AI model generated the text</span>
</div>
</div>
<div class="option-row">
<label class="option-label">Enable Sentence Highlighting:</label>
<div class="checkbox-wrapper">
<input type="checkbox" id="enable-highlighting" checked>
<span style="font-size: 0.85rem; color: var(--text-muted);">Show suspicious sentences</span>
</div>
</div>
<!-- NEW OPTIONS -->
<div class="option-row">
<label class="option-label">Sentence-Level Analysis:</label>
<div class="checkbox-wrapper">
<input type="checkbox" id="use-sentence-level" checked>
<span style="font-size: 0.85rem; color: var(--text-muted);">More accurate but slower analysis</span>
</div>
</div>
<div class="option-row">
<label class="option-label">Include Metrics Summary:</label>
<div class="checkbox-wrapper">
<input type="checkbox" id="include-metrics-summary" checked>
<span style="font-size: 0.85rem; color: var(--text-muted);">Show text analysis statistics</span>
</div>
</div>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 1rem;">
<button id="analyze-btn" class="analyze-btn">
🔍 Analyze Text
</button>
<div class="action-buttons">
<button id="refresh-btn" class="action-btn refresh">
🔄 Refresh
</button>
<button id="try-next-btn" class="action-btn">
➕ Try Next
</button>
</div>
</div>
</div>
<!-- Right Panel: Results -->
<div class="panel">
<h2 class="panel-title">Analysis Report</h2>
<div class="panel-content">
<div class="report-tabs">
<button class="report-tab active" data-report="summary">
📊 Summary
</button>
<button class="report-tab" data-report="highlighted">
📝 Highlighted Text
</button>
<button class="report-tab" data-report="metrics">
ℹ️ Detailed Metrics
</button>
</div>
<!-- Summary Report -->
<div id="summary-report" class="report-content active">
<div class="empty-state">
<div class="empty-icon"></div>
<h3 class="empty-title">Ready for Analysis</h3>
<p class="empty-description">
Paste text or upload a document to begin comprehensive AI detection analysis.
Our 6-metric ensemble will provide detailed insights.
</p>
</div>
</div>
<!-- Highlighted Text Report -->
<div id="highlighted-report" class="report-content">
<div class="empty-state">
<div class="empty-icon">📝</div>
<p class="empty-description">
Run an analysis to see sentence-level highlighting
</p>
</div>
</div>
<!-- Metrics Report -->
<div id="metrics-report" class="report-content">
<div class="empty-state">
<div class="empty-icon">📊</div>
<p class="empty-description">
Run an analysis to see detailed metric breakdowns
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Configuration
const API_BASE = '';
let currentAnalysisData = null;
let currentMetricIndex = 0;
let totalMetrics = 0;
// Navigation
function showLanding() {
document.getElementById('landing-page').style.display = 'block';
document.getElementById('analysis-interface').style.display = 'none';
window.scrollTo(0, 0);
}
function showAnalysis() {
document.getElementById('landing-page').style.display = 'none';
document.getElementById('analysis-interface').style.display = 'block';
window.scrollTo(0, 0);
resetAnalysisInterface();
}
// Reset analysis interface
function resetAnalysisInterface() {
// Clear text input
document.getElementById('text-input').value = '';
// Clear file input and display
document.getElementById('file-input').value = '';
document.getElementById('file-name-display').style.display = 'none';
document.getElementById('file-name-display').innerHTML = '';
// Reset tabs to paste
document.querySelectorAll('.input-tab').forEach(t => t.classList.remove('active'));
document.querySelector('.input-tab[data-tab="paste"]').classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.getElementById('paste-tab').classList.add('active');
// Reset options to defaults
document.getElementById('domain-select').value = '';
document.getElementById('enable-attribution').checked = true;
document.getElementById('enable-highlighting').checked = true;
document.getElementById('use-sentence-level').checked = true;
document.getElementById('include-metrics-summary').checked = true;
// Reset report tabs to summary
document.querySelectorAll('.report-tab').forEach(t => t.classList.remove('active'));
document.querySelector('.report-tab[data-report="summary"]').classList.add('active');
document.querySelectorAll('.report-content').forEach(content => content.classList.remove('active'));
document.getElementById('summary-report').classList.add('active');
// Show empty state
document.getElementById('summary-report').innerHTML = `
<div class="empty-state">
<div class="empty-icon">✓</div>
<h3 class="empty-title">Ready for Analysis</h3>
<p class="empty-description">
Paste text or upload a document to begin comprehensive AI detection analysis.
Our 6-metric ensemble will provide detailed insights.
</p>
</div>
`;
document.getElementById('highlighted-report').innerHTML = `
<div class="empty-state">
<div class="empty-icon">📝</div>
<p class="empty-description">
Run an analysis to see sentence-level highlighting
</p>
</div>
`;
document.getElementById('metrics-report').innerHTML = `
<div class="empty-state">
<div class="empty-icon">📊</div>
<p class="empty-description">
Run an analysis to see detailed metric breakdowns
</p>
</div>
`;
// Clear current analysis data
currentAnalysisData = null;
currentMetricIndex = 0;
totalMetrics = 0;
}
// Input Tab Switching
document.querySelectorAll('.input-tab').forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
document.querySelectorAll('.input-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('#paste-tab, #upload-tab').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tabName}-tab`).classList.add('active');
});
});
// Report Tab Switching
document.querySelectorAll('.report-tab').forEach(tab => {
tab.addEventListener('click', () => {
const reportName = tab.dataset.report;
document.querySelectorAll('.report-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.report-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${reportName}-report`).classList.add('active');
});
});
// File Upload Handling
const fileInput = document.getElementById('file-input');
const fileUploadArea = document.getElementById('file-upload-area');
const fileNameDisplay = document.getElementById('file-name-display');
fileUploadArea.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
handleFileSelect(e.target.files[0]);
});
// Drag and Drop
fileUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
fileUploadArea.classList.add('drag-over');
});
fileUploadArea.addEventListener('dragleave', () => {
fileUploadArea.classList.remove('drag-over');
});
fileUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
fileUploadArea.classList.remove('drag-over');
const file = e.dataTransfer.files[0];
if (file) {
fileInput.files = e.dataTransfer.files;
handleFileSelect(file);
}
});
function handleFileSelect(file) {
if (!file) return;
const allowedTypes = ['.txt', '.pdf', '.docx', '.doc', '.md'];
const fileExt = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedTypes.includes(fileExt)) {
alert('Unsupported file type. Please upload: TXT, PDF, DOCX, DOC, or MD files.');
return;
}
if (file.size > 10 * 1024 * 1024) {
alert('File size exceeds 10MB limit.');
return;
}
fileNameDisplay.style.display = 'block';
fileNameDisplay.innerHTML = `
<strong>Selected file:</strong> ${file.name}
<span style="color: var(--text-muted);">(${formatFileSize(file.size)})</span>
`;
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
// Analyze Button
document.getElementById('analyze-btn').addEventListener('click', async () => {
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
const textInput = document.getElementById('text-input').value.trim();
const fileInput = document.getElementById('file-input').files[0];
if (activeTab === 'paste' && !textInput) {
alert('Please paste some text to analyze (minimum 50 characters).');
return;
}
if (activeTab === 'paste' && textInput.length < 50) {
alert('Text must be at least 50 characters long for accurate analysis.');
return;
}
if (activeTab === 'upload' && !fileInput) {
alert('Please select a file to upload.');
return;
}
await performAnalysis(activeTab, textInput, fileInput);
});
// Refresh Button - clears everything and shows empty state
document.getElementById('refresh-btn').addEventListener('click', () => {
resetAnalysisInterface();
});
// Try Next Button - same as refresh but keeps the interface ready
document.getElementById('try-next-btn').addEventListener('click', () => {
resetAnalysisInterface();
});
async function performAnalysis(mode, text, file) {
const analyzeBtn = document.getElementById('analyze-btn');
analyzeBtn.disabled = true;
analyzeBtn.innerHTML = '⏳ Analyzing...';
showLoading();
try {
let response;
if (mode === 'paste') {
response = await analyzeText(text);
} else {
response = await analyzeFile(file);
}
currentAnalysisData = response;
displayResults(response);
} catch (error) {
console.error('Analysis error:', error);
showError(error.message || 'Analysis failed. Please try again.');
} finally {
analyzeBtn.disabled = false;
analyzeBtn.innerHTML = '🔍 Analyze Text';
}
}
async function analyzeText(text) {
const domain = document.getElementById('domain-select').value || null;
const enableAttribution = document.getElementById('enable-attribution').checked;
const enableHighlighting = document.getElementById('enable-highlighting').checked;
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
const response = await fetch(`${API_BASE}/api/analyze`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: text,
domain: domain,
enable_attribution: enableAttribution,
enable_highlighting: enableHighlighting,
use_sentence_level: useSentenceLevel,
include_metrics_summary: includeMetricsSummary,
skip_expensive_metrics: false
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Analysis failed');
}
return await response.json();
}
async function analyzeFile(file) {
const domain = document.getElementById('domain-select').value || null;
const enableAttribution = document.getElementById('enable-attribution').checked;
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
const formData = new FormData();
formData.append('file', file);
if (domain) formData.append('domain', domain);
formData.append('enable_attribution', enableAttribution.toString());
formData.append('use_sentence_level', useSentenceLevel.toString());
formData.append('include_metrics_summary', includeMetricsSummary.toString());
formData.append('skip_expensive_metrics', 'false');
const response = await fetch(`${API_BASE}/api/analyze/file`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'File analysis failed');
}
return await response.json();
}
function showLoading() {
document.getElementById('summary-report').innerHTML = `
<div class="loading">
<div class="spinner"></div>
<p style="color: var(--text-secondary);">Analyzing content with 6-metric ensemble...</p>
<p style="color: var(--text-muted); font-size: 0.9rem; margin-top: 0.5rem;">
This may take a few seconds
</p>
</div>
`;
}
function showError(message) {
document.getElementById('summary-report').innerHTML = `
<div class="empty-state">
<div class="empty-icon" style="background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);">⚠️</div>
<h3 class="empty-title">Analysis Failed</h3>
<p class="empty-description">${message}</p>
</div>
`;
}
function displayResults(data) {
console.log('Response data:', data);
// Handle different response structures
const detection = data.detection_result;
if (!detection) {
showError('Invalid response structure. Please check the API response format.');
console.error('Full response:', data);
return;
}
// Extract data based on your actual API structure
const ensemble = detection.ensemble_result || detection.ensemble;
const prediction = detection.prediction || {};
const metrics = detection.metric_results || detection.metrics;
const analysis = detection.analysis || {};
// Display Summary with enhanced reasoning
displaySummary(ensemble, prediction, analysis, data.attribution, data.reasoning);
// Display Highlighted Text with enhanced features
if (data.highlighted_html) {
displayHighlightedText(data.highlighted_html);
} else {
document.getElementById('highlighted-report').innerHTML = `
<div class="empty-state">
<p class="empty-description">Highlighting not available for this analysis</p>
</div>
`;
}
// Display Metrics with carousel
if (metrics && Object.keys(metrics).length > 0) {
displayMetricsCarousel(metrics, analysis, ensemble);
} else {
document.getElementById('metrics-report').innerHTML = `
<div class="empty-state">
<p class="empty-description">Metric details not available</p>
</div>
`;
}
}
function displaySummary(ensemble, prediction, analysis, attribution, reasoning) {
// Extract and validate data with fallbacks
const {
aiProbability,
humanProbability,
mixedProbability,
verdict,
confidence,
domain,
isAI,
gaugeColor,
gaugeDegree,
confidenceLevel,
confidenceClass
} = extractSummaryData(ensemble, analysis);
// Generate attribution HTML with proper filtering
const attributionHTML = generateAttributionHTML(attribution);
document.getElementById('summary-report').innerHTML = `
<div class="result-summary">
${createGaugeSection(aiProbability, humanProbability, mixedProbability, gaugeColor, gaugeDegree)}
${createInfoGrid(verdict, confidence, confidenceClass, domain, mixedProbability)}
${createEnhancedReasoningHTML(ensemble, analysis, reasoning)}
${attributionHTML}
${createDownloadActions()}
</div>
`;
}
// Helper function to extract and validate summary data
function extractSummaryData(ensemble, analysis) {
const aiProbability = ensemble.ai_probability !== undefined ?
(ensemble.ai_probability * 100).toFixed(0) : '0';
const humanProbability = ensemble.human_probability !== undefined ?
(ensemble.human_probability * 100).toFixed(0) : '0';
const mixedProbability = ensemble.mixed_probability !== undefined ?
(ensemble.mixed_probability * 100).toFixed(0) : '0';
const verdict = ensemble.final_verdict || 'Unknown';
const confidence = ensemble.overall_confidence !== undefined ?
(ensemble.overall_confidence * 100).toFixed(1) : '0';
const domain = analysis.domain || 'general';
const isAI = verdict.toLowerCase().includes('ai');
const gaugeColor = isAI ? 'var(--danger)' : 'var(--success)';
const gaugeDegree = aiProbability * 3.6;
const confidenceLevel = getConfidenceLevel(parseFloat(confidence));
const confidenceClass = getConfidenceClass(confidenceLevel);
return {
aiProbability,
humanProbability,
mixedProbability,
verdict,
confidence,
domain,
isAI,
gaugeColor,
gaugeDegree,
confidenceLevel,
confidenceClass
};
}
// Helper function to determine confidence level
function getConfidenceLevel(confidence) {
if (confidence >= 70) return 'HIGH';
if (confidence >= 40) return 'MEDIUM';
return 'LOW';
}
// Helper function to get confidence CSS class
function getConfidenceClass(confidenceLevel) {
const classMap = {
'HIGH': 'confidence-high',
'MEDIUM': 'confidence-medium',
'LOW': 'confidence-low'
};
return classMap[confidenceLevel] || 'confidence-low';
}
// Helper function to generate attribution HTML with filtering
function generateAttributionHTML(attribution) {
if (!attribution || !attribution.predicted_model) {
return '';
}
const modelName = formatModelName(attribution.predicted_model);
const modelConf = attribution.confidence ?
(attribution.confidence * 100).toFixed(1) : 'N/A';
const topModelsHTML = generateTopModelsHTML(attribution.model_probabilities);
const reasoningHTML = generateAttributionReasoningHTML(attribution.reasoning);
// Only show attribution if confidence is meaningful (>30%)
if (attribution.confidence > 0.3) {
return `
<div class="attribution-section">
<div class="attribution-title">🤖 AI Model Attribution</div>
${topModelsHTML}
<div class="attribution-confidence">
Attribution Confidence: <strong>${modelConf}%</strong>
</div>
${reasoningHTML}
</div>
`;
}
return '';
}
// Helper function to generate top models HTML with filtering
function generateTopModelsHTML(modelProbabilities) {
if (!modelProbabilities) {
return '<div class="attribution-uncertain">Model probabilities not available</div>';
}
// Filter and sort models
const meaningfulModels = Object.entries(modelProbabilities)
.sort((a, b) => b[1] - a[1])
.filter(([model, prob]) => prob > 0.15) // Only show models with >15% probability
.slice(0, 3); // Show top 3
if (meaningfulModels.length === 0) {
return `
<div class="attribution-uncertain">
Model attribution uncertain - text patterns don't strongly match any specific AI model
</div>
`;
}
return meaningfulModels.map(([model, prob]) =>
`<div class="model-match">
<span class="model-name">${formatModelName(model)}</span>
<span class="model-confidence">${(prob * 100).toFixed(1)}%</span>
</div>`
).join('');
}
// Helper function to format model names
function formatModelName(modelName) {
return modelName.replace(/_/g, ' ').replace(/-/g, ' ').toUpperCase();
}
// Helper function to generate attribution reasoning HTML
function generateAttributionReasoningHTML(reasoning) {
if (!reasoning || !Array.isArray(reasoning) || reasoning.length === 0) {
return '';
}
return `
<div class="attribution-reasoning">
${reasoning[0]}
</div>
`;
}
// Helper function to create single-progress gauge section
function createGaugeSection(aiProbability, humanProbability, mixedProbability, gaugeColor, gaugeDegree) {
// Ensure these are numbers
const ai = parseFloat(aiProbability);
const human = parseFloat(humanProbability);
const mixed = parseFloat(mixedProbability);
// Determine which probability is highest
let maxValue, maxColor, maxLabel;
if (ai >= human && ai >= mixed) {
maxValue = ai;
maxColor = 'var(--danger)';
maxLabel = 'AI Probability';
} else if (human >= ai && human >= mixed) {
maxValue = human;
maxColor = 'var(--success)';
maxLabel = 'Human Probability';
} else {
maxValue = mixed;
maxColor = 'var(--primary)';
maxLabel = 'Mixed Probability';
}
console.log('Selected:', { maxValue, maxLabel });
// Calculate the degree for the progress (maxValue% of 360 degrees)
const progressDegree = (maxValue / 100) * 360;
return `
<div class="gauge-container">
<div class="single-progress-gauge" style="
background: conic-gradient(
${maxColor} 0deg,
${maxColor} ${progressDegree}deg,
rgba(51, 65, 85, 0.3) ${progressDegree}deg,
rgba(51, 65, 85, 0.3) 360deg
);
">
<div class="gauge-inner">
<div class="gauge-value" style="color: ${maxColor}">${maxValue}%</div>
<div class="gauge-label">${maxLabel}</div>
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1.5rem 0;">
<div style="text-align: center; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid rgba(239, 68, 68, 0.3);">
<div style="font-size: 0.85rem; color: var(--danger); margin-bottom: 0.25rem; font-weight: 600;">AI</div>
<div style="font-size: 1.4rem; font-weight: 700; color: var(--danger);">${aiProbability}%</div>
</div>
<div style="text-align: center; padding: 1rem; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.3);">
<div style="font-size: 0.85rem; color: var(--success); margin-bottom: 0.25rem; font-weight: 600;">Human</div>
<div style="font-size: 1.4rem; font-weight: 700; color: var(--success);">${humanProbability}%</div>
</div>
<div style="text-align: center; padding: 1rem; background: rgba(6, 182, 212, 0.1); border-radius: 8px; border: 1px solid rgba(6, 182, 212, 0.3);">
<div style="font-size: 0.85rem; color: var(--primary); margin-bottom: 0.25rem; font-weight: 600;">Mixed</div>
<div style="font-size: 1.4rem; font-weight: 700; color: var(--primary);">${mixedProbability}%</div>
</div>
</div>
<style>
.single-progress-gauge {
width: 220px;
height: 220px;
margin: 0 auto 2rem;
position: relative;
border-radius: 50%;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.gauge-inner {
position: absolute;
width: 170px;
height: 170px;
background: var(--bg-panel);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gauge-value {
font-size: 3rem;
font-weight: 800;
}
.gauge-label {
font-size: 0.9rem;
color: var(--text-secondary);
margin-top: 0.25rem;
}
</style>
`;
}
// Helper function to create info grid
function createInfoGrid(verdict, confidence, confidenceClass, domain, mixedProbability) {
const mixedContentInfo = mixedProbability > 10 ?
`<div style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--primary);">
🔀 ${mixedProbability}% Mixed Content Detected
</div>` : '';
return `
<div class="result-info-grid">
<div class="info-card">
<div class="info-label">Verdict</div>
<div class="info-value verdict-text">${verdict}</div>
${mixedContentInfo}
</div>
<div class="info-card">
<div class="info-label">Confidence Level</div>
<div class="info-value">
<span class="confidence-badge ${confidenceClass}">${confidence}%</span>
</div>
</div>
<div class="info-card">
<div class="info-label">Content Domain</div>
<div class="info-value domain-text">${formatDomainName(domain)}</div>
</div>
</div>
`;
}
// Helper function to create download actions
function createDownloadActions() {
return `
<div class="download-actions">
<button class="download-btn" onclick="downloadReport('json')">
📄 Download JSON
</button>
<button class="download-btn" onclick="downloadReport('pdf')">
📑 Download PDF Report
</button>
</div>
`;
}
// Helper function to format domain names
function formatDomainName(domain) {
return domain.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
}
function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
// Use reasoning data if available
if (reasoning && reasoning.summary) {
// Process the summary into bullet points
const bulletPoints = formatSummaryAsBulletPoints(reasoning.summary, ensemble, analysis);
// Process key indicators with markdown formatting
let processedIndicators = [];
if (reasoning.key_indicators && reasoning.key_indicators.length > 0) {
processedIndicators = reasoning.key_indicators.map(indicator => {
let processedIndicator = indicator;
// Remove HTML entities
processedIndicator = processedIndicator.replace(/&ast;/g, '*')
.replace(/&#42;/g, '*');
// Process bold formatting
processedIndicator = processedIndicator.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
// Clean up remaining asterisks
processedIndicator = processedIndicator.replace(/\*\*/g, '')
.replace(/\*(?![^<]*>)/g, '');
// Replace underscores with spaces
processedIndicator = processedIndicator.replace(/_/g, ' ');
return processedIndicator;
});
}
return `
<div class="reasoning-box enhanced">
<div class="reasoning-header">
<div class="reasoning-icon">💡</div>
<div class="reasoning-title">Detection Reasoning</div>
<div class="confidence-tag ${ensemble.overall_confidence >= 0.7 ? 'high-confidence' : ensemble.overall_confidence >= 0.4 ? 'medium-confidence' : 'low-confidence'}">
${ensemble.overall_confidence >= 0.7 ? 'High Confidence' : ensemble.overall_confidence >= 0.4 ? 'Medium Confidence' : 'Low Confidence'}
</div>
</div>
<div class="verdict-summary">
<div class="verdict-text">${ensemble.final_verdict}</div>
<div class="probability">AI Probability: <span class="probability-value">${(ensemble.ai_probability * 100).toFixed(2)}%</span></div>
</div>
<div class="reasoning-bullet-points">
${bulletPoints}
</div>
${processedIndicators.length > 0 ? `
<div class="metrics-breakdown">
<div class="breakdown-header" style="text-align: center; font-weight: 700; color: var(--text-secondary); margin-bottom: 1rem;">
KEY INDICATORS
</div>
${processedIndicators.map(indicator => {
// Split indicator into metric name and sub-metric details
const colonIndex = indicator.indexOf(':');
if (colonIndex !== -1) {
const metricName = indicator.substring(0, colonIndex).trim();
const metricDetails = indicator.substring(colonIndex + 1).trim();
return `
<div style="margin-bottom: 1rem; text-align: left;">
<div style="font-weight: 700; color: #fff; text-align: center; margin-bottom: 0.5rem; font-size: 1rem;">
${metricName}
</div>
<div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4; text-align: left;">
${metricDetails}
</div>
</div>
`;
} else {
// If no colon, treat as general indicator
return `
<div style="margin-bottom: 1rem; text-align: left;">
<div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4;">
${indicator}
</div>
</div>
`;
}
}).join('')}
</div>
` : ''}
${ensemble.consensus_level > 0.7 ? `
<div class="agreement-indicator">
<div class="agreement-icon">✓</div>
<div class="agreement-text">Strong metric consensus (${(ensemble.consensus_level * 100).toFixed(1)}%)</div>
</div>
` : ''}
</div>
`;
}
// Fallback to basic reasoning if no reasoning data
return `
<div class="reasoning-box">
<div class="reasoning-title">💡 Detection Reasoning</div>
<p class="reasoning-text" style="text-align: left;">
Analysis based on 6-metric ensemble with domain-aware calibration.
The system evaluated linguistic patterns, statistical features, and semantic structures
to determine content authenticity with ${(ensemble.overall_confidence * 100).toFixed(1)}% confidence.
</p>
</div>
`;
}
// Helper function to format summary as bullet points
function formatSummaryAsBulletPoints(summary, ensemble, analysis) {
let processedSummary = summary;
// Remove any existing HTML entities for asterisks first
processedSummary = processedSummary.replace(/&ast;/g, '*')
.replace(/&#42;/g, '*');
// Process markdown bold formatting
processedSummary = processedSummary.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
// Final cleanup: remove any remaining standalone asterisks that weren't processed
processedSummary = processedSummary.replace(/\*\*/g, '')
.replace(/\*(?![^<]*>)/g, '');
// Split the summary into sentences/phrases for bullet points
const sentences = processedSummary.split(/\.\s+/);
// Create bullet points from key information
const bulletPoints = [];
// Add confidence level as first bullet
const confidenceLevel = ensemble.overall_confidence >= 0.7 ? 'High Confidence' :
ensemble.overall_confidence >= 0.4 ? 'Medium Confidence' : 'Low Confidence';
bulletPoints.push(`<div class="bullet-point">• ${confidenceLevel}</div>`);
// Add verdict as second bullet
bulletPoints.push(`<div class="bullet-point">• ${ensemble.final_verdict}</div>`);
// Add AI probability as third bullet
bulletPoints.push(`<div class="bullet-point">• AI Probability: ${(ensemble.ai_probability * 100).toFixed(2)}%</div>`);
// Add the main analysis sentences as individual bullets
sentences.forEach(sentence => {
if (sentence.trim() &&
!sentence.includes('confidence') &&
!sentence.includes(ensemble.final_verdict) &&
!sentence.includes('AI probability')) {
// Clean up the sentence and add as bullet
let cleanSentence = sentence.trim();
if (!cleanSentence.endsWith('.')) {
cleanSentence += '.';
}
bulletPoints.push(`<div class="bullet-point">• ${cleanSentence}</div>`);
}
});
return bulletPoints.join('');
}
function displayHighlightedText(html) {
document.getElementById('highlighted-report').innerHTML = `
${createDefaultLegend()}
<div class="highlighted-text">
${html}
</div>
${getHighlightStyles()}
`;
}
function createDefaultLegend() {
return `
<div class="highlight-legend">
<div class="legend-item">
<div class="legend-color" style="background: #fecaca;"></div>
<div class="legend-label">Very Likely AI (90-100%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fed7aa;"></div>
<div class="legend-label">Likely AI (75-90%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fde68a;"></div>
<div class="legend-label">Possibly AI (60-75%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fef9c3;"></div>
<div class="legend-label">Uncertain (40-60%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #86efac;"></div>
<div class="legend-label">Possibly Human (25-40%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #bbf7d0;"></div>
<div class="legend-label">Likely Human (10-25%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #dcfce7;"></div>
<div class="legend-label">Very Likely Human (0-10%)</div>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #e9d5ff;"></div>
<div class="legend-label">Mixed Content</div>
</div>
</div>
`;
}
function getHighlightStyles() {
return `
<style>
#highlighted-report .highlight {
padding: 2px 4px;
margin: 0 1px;
border-radius: 3px;
cursor: help;
transition: all 0.2s;
border-bottom: 2px solid transparent;
color: #000000 !important;
font-weight: 500;
}
#highlighted-report .highlight:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10;
text-shadow: 0 1px 1px rgba(255,255,255,0.8);
}
#highlighted-report .very-high-ai {
background-color: #fee2e2 !important;
border-bottom-color: #ef4444 !important;
}
#highlighted-report .high-ai {
background-color: #fed7aa !important;
border-bottom-color: #f97316 !important;
}
#highlighted-report .medium-ai {
background-color: #fef3c7 !important;
border-bottom-color: #f59e0b !important;
}
#highlighted-report .uncertain {
background-color: #fef9c3 !important;
border-bottom-color: #fbbf24 !important;
}
#highlighted-report .medium-human {
background-color: #ecfccb !important;
border-bottom-color: #a3e635 !important;
}
#highlighted-report .high-human {
background-color: #bbf7d0 !important;
border-bottom-color: #4ade80 !important;
}
#highlighted-report .very-high-human {
background-color: #dcfce7 !important;
border-bottom-color: #22c55e !important;
}
#highlighted-report .mixed-content {
background-color: #e9d5ff !important;
border-bottom-color: #a855f7 !important;
background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(168, 85, 247, 0.1) 5px, rgba(168, 85, 247, 0.1) 10px) !important;
}
</style>
`;
}
function displayMetricsCarousel(metrics, analysis, ensemble) {
const metricOrder = ['structural', 'perplexity', 'entropy', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability'];
const availableMetrics = metricOrder.filter(key => metrics[key]);
totalMetrics = availableMetrics.length;
if (totalMetrics === 0) {
document.getElementById('metrics-report').innerHTML = `
<div class="empty-state">
<p class="empty-description">No metric details available</p>
</div>
`;
return;
}
let carouselHTML = `
<div class="metrics-carousel-container">
<div class="metrics-carousel-content">
`;
availableMetrics.forEach((metricKey, index) => {
const metric = metrics[metricKey];
if (!metric) return;
const aiProb = (metric.ai_probability * 100).toFixed(1);
const humanProb = (metric.human_probability * 100).toFixed(1);
const mixedProb = (metric.mixed_probability * 100).toFixed(1);
const confidence = (metric.confidence * 100).toFixed(1);
const weight = ensemble.metric_contributions && ensemble.metric_contributions[metricKey] ?
(ensemble.metric_contributions[metricKey].weight * 100).toFixed(1) : '0.0';
// Determine verdict based on probabilities
let verdictText, verdictClass;
if (metric.mixed_probability > 0.3) {
verdictText = 'MIXED';
verdictClass = 'verdict-mixed';
} else if (metric.ai_probability >= 0.6) {
verdictText = 'AI';
verdictClass = 'verdict-ai';
} else if (metric.ai_probability >= 0.4) {
verdictText = 'UNCERTAIN';
verdictClass = 'verdict-uncertain';
} else {
verdictText = 'HUMAN';
verdictClass = 'verdict-human';
}
carouselHTML += `
<div class="metric-slide ${index === 0 ? 'active' : ''}" data-metric-index="${index}">
<div class="metric-result-card">
<div class="metric-header">
<div class="metric-name">${formatMetricName(metricKey)}</div>
</div>
<div class="metric-description">
${getMetricDescription(metricKey)}
</div>
<!-- Enhanced Probability Display with Mixed -->
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1rem 0;">
<div style="text-align: center;">
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">AI</div>
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
<div style="background: var(--danger); height: 100%; width: ${aiProb}%; transition: width 0.5s;"></div>
</div>
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${aiProb}%</div>
</div>
<div style="text-align: center;">
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Human</div>
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
<div style="background: var(--success); height: 100%; width: ${humanProb}%; transition: width 0.5s;"></div>
</div>
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${humanProb}%</div>
</div>
<div style="text-align: center;">
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Mixed</div>
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
<div style="background: var(--primary); height: 100%; width: ${mixedProb}%; transition: width 0.5s;"></div>
</div>
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${mixedProb}%</div>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin: 0.75rem 0;">
<span class="metric-verdict ${verdictClass}">${verdictText}</span>
<span style="font-size: 0.85rem; color: var(--text-secondary);">Confidence: ${confidence}% | Weight: ${weight}%</span>
</div>
${metric.details ? renderMetricDetails(metricKey, metric.details) : ''}
</div>
</div>
`;
});
carouselHTML += `
</div>
<div class="metrics-carousel-nav">
<button class="carousel-btn prev-btn" onclick="navigateMetrics(-1)" ${currentMetricIndex === 0 ? 'disabled' : ''}>← Previous</button>
<div class="carousel-position">${currentMetricIndex + 1} / ${totalMetrics}</div>
<button class="carousel-btn next-btn" onclick="navigateMetrics(1)" ${currentMetricIndex === totalMetrics - 1 ? 'disabled' : ''}>Next →</button>
</div>
</div>
`;
document.getElementById('metrics-report').innerHTML = carouselHTML;
updateCarouselButtons();
}
function navigateMetrics(direction) {
const newMetricIndex = currentMetricIndex + direction;
if (newMetricIndex >= 0 && newMetricIndex < totalMetrics) {
currentMetricIndex = newMetricIndex;
updateMetricCarousel();
}
}
function updateMetricCarousel() {
const slides = document.querySelectorAll('.metric-slide');
slides.forEach((slide, index) => {
if (index === currentMetricIndex) {
slide.classList.add('active');
} else {
slide.classList.remove('active');
}
});
updateCarouselButtons();
// Update position indicator
const positionElement = document.querySelector('.carousel-position');
if (positionElement) {
positionElement.textContent = `${currentMetricIndex + 1} / ${totalMetrics}`;
}
}
function updateCarouselButtons() {
const prevBtn = document.querySelector('.prev-btn');
const nextBtn = document.querySelector('.next-btn');
if (prevBtn) {
prevBtn.disabled = currentMetricIndex === 0;
}
if (nextBtn) {
nextBtn.disabled = currentMetricIndex === totalMetrics - 1;
}
}
function renderMetricDetails(metricName, details) {
if (!details || Object.keys(details).length === 0) return '';
// Key metrics to show for each type
const importantKeys = {
'structural': ['burstiness_score', 'length_uniformity', 'avg_sentence_length', 'std_sentence_length'],
'perplexity': ['overall_perplexity', 'avg_sentence_perplexity', 'normalized_perplexity'],
'entropy': ['token_diversity', 'sequence_unpredictability', 'char_entropy'],
'semantic_analysis': ['coherence_score', 'consistency_score', 'repetition_score'],
'linguistic': ['pos_diversity', 'syntactic_complexity', 'grammatical_consistency'],
'multi_perturbation_stability': ['stability_score', 'curvature_score', 'likelihood_ratio', 'perturbation_variance', 'mixed_probability']
};
const keysToShow = importantKeys[metricName] || Object.keys(details).slice(0, 6);
let detailsHTML = '<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);">';
detailsHTML += '<div style="font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.75rem;">📈 Detailed Metrics:</div>';
detailsHTML += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; font-size: 0.85rem;">';
keysToShow.forEach(key => {
if (details[key] !== undefined && details[key] !== null) {
let value = details[key];
let displayValue;
// Format values appropriately
if (typeof value === 'number') {
if (key.includes('score') || key.includes('ratio') || key.includes('probability')) {
displayValue = (value * 100).toFixed(2) + '%';
} else if (value < 1 && value > 0) {
displayValue = value.toFixed(4);
} else {
displayValue = value.toFixed(2);
}
} else {
displayValue = value;
}
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
detailsHTML += `
<div style="background: rgba(15, 23, 42, 0.6); padding: 0.5rem; border-radius: 6px;">
<div style="color: var(--text-muted); font-size: 0.75rem; margin-bottom: 0.25rem;">${label}</div>
<div style="color: var(--primary); font-weight: 700;">${displayValue}</div>
</div>
`;
}
});
detailsHTML += '</div></div>';
return detailsHTML;
}
function getMetricDescription(metricName) {
const descriptions = {
structural: 'Analyzes sentence structure, length patterns, and statistical features.',
perplexity: 'Measures text predictability using language model cross-entropy.',
entropy: 'Evaluates token diversity and sequence unpredictability.',
semantic_analysis: 'Examines semantic coherence, topic consistency, and logical flow.',
linguistic: 'Assesses grammatical patterns, syntactic complexity, and style markers.',
multi_perturbation_stability: 'Tests text stability under perturbation using curvature analysis.'
};
return descriptions[metricName] || 'Metric analysis complete.';
}
function formatMetricName(name) {
const names = {
structural: 'Structural Analysis',
perplexity: 'Perplexity',
entropy: 'Entropy',
semantic_analysis: 'Semantic Analysis',
linguistic: 'Linguistic Analysis',
multi_perturbation_stability: 'Multi-Perturbation Stability'
};
return names[name] || name.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
}
async function downloadReport(format) {
if (!currentAnalysisData) {
alert('No analysis data available');
return;
}
try {
const analysisId = currentAnalysisData.analysis_id;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
// For JSON, download directly from current data
if (format === 'json') {
const data = {
...currentAnalysisData,
download_timestamp: new Date().toISOString(),
report_version: '2.0.0'
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json'
});
const filename = `ai-detection-report-${analysisId}-${timestamp}.json`;
await downloadBlob(blob, filename);
return;
}
// Get the original text for report generation
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
let textToSend = '';
if (activeTab === 'paste') {
textToSend = document.getElementById('text-input').value;
} else {
textToSend = currentAnalysisData.detection_result?.processed_text?.text ||
'Uploaded file content - see analysis for details';
}
// For PDF, request from server
const formData = new FormData();
formData.append('analysis_id', analysisId);
formData.append('text', textToSend);
formData.append('formats', format);
formData.append('include_highlights', document.getElementById('enable-highlighting').checked.toString());
const response = await fetch(`${API_BASE}/api/report/generate`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Report generation failed');
}
const result = await response.json();
if (result.reports && result.reports[format]) {
const filename = result.reports[format];
const downloadResponse = await fetch(`${API_BASE}/api/report/download/${filename}`);
if (!downloadResponse.ok) {
throw new Error('Failed to download file');
}
const blob = await downloadResponse.blob();
const downloadFilename = `ai-detection-${format}-report-${analysisId}-${timestamp}.${format}`;
await downloadBlob(blob, downloadFilename);
} else {
alert('Report file not available');
}
} catch (error) {
console.error('Download error:', error);
alert('Failed to download report. Please try again.');
}
}
async function downloadBlob(blob, filename) {
try {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
showDownloadSuccess(filename);
}, 100);
} catch (error) {
console.error('Download failed:', error);
alert('Download failed. Please try again.');
}
}
function showDownloadSuccess(filename) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: var(--success);
color: white;
padding: 1rem 1.5rem;
border-radius: 8px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10000;
animation: slideIn 0.3s ease;
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span>✓</span>
<span>Downloaded: ${filename}</span>
</div>
`;
document.body.appendChild(notification);
if (!document.querySelector('#download-animation')) {
const style = document.createElement('style');
style.id = 'download-animation';
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(style);
}
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
if (href !== '#') {
e.preventDefault();
const target = document.querySelector(href);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
});
});
// Initialize - show landing page by default
showLanding();
</script>
</body>
</html>