Spaces:
Sleeping
Sleeping
| <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 ; | |
| } | |
| .domain-text { | |
| font-size: 1.1rem ; | |
| } | |
| .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>© 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) { | |
| // Determine which probability is highest | |
| let maxValue, maxColor, maxLabel; | |
| if (aiProbability >= humanProbability && aiProbability >= mixedProbability) { | |
| maxValue = aiProbability; | |
| maxColor = 'var(--danger)'; | |
| maxLabel = 'AI Probability'; | |
| } else if (humanProbability >= aiProbability && humanProbability >= mixedProbability) { | |
| maxValue = humanProbability; | |
| maxColor = 'var(--success)'; | |
| maxLabel = 'Human Probability'; | |
| } else { | |
| maxValue = mixedProbability; | |
| maxColor = 'var(--primary)'; | |
| maxLabel = 'Mixed Probability'; | |
| } | |
| // 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(/*/g, '*') | |
| .replace(/*/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(/*/g, '*') | |
| .replace(/*/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> |