"""
Advanced AI Text Detector - Enhanced Results Display & API
4-Category Classification with improved UX and JSON API support
"""
import gradio as gr
import torch
import numpy as np
import re
import time
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from typing import Dict, List, Tuple
import statistics
import string
from collections import Counter
import json
import plotly.graph_objects as go
import plotly.express as px
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
class ImprovedAIDetector:
"""
Enhanced AI text detector with 4-category classification and improved accuracy
"""
def __init__(self):
self.tokenizer = None
self.model = None
self.load_models()
def load_models(self):
"""Load and cache detection models"""
try:
model_name = "roberta-base-openai-detector"
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
print("✓ Models loaded successfully")
except Exception as e:
print(f"⚠️ Model loading failed: {e}")
self.tokenizer = None
self.model = None
def extract_linguistic_features(self, text: str) -> Dict[str, float]:
"""Extract comprehensive linguistic features for detection"""
if len(text.strip()) < 10:
return {}
sentences = re.split(r'[.!?]+', text)
sentences = [s.strip() for s in sentences if s.strip()]
words = text.split()
if not sentences or not words:
return {}
features = {}
# Length-based features
features['avg_sentence_length'] = np.mean([len(s.split()) for s in sentences])
features['avg_word_length'] = np.mean([len(word) for word in words])
features['total_words'] = len(words)
# Vocabulary diversity
unique_words = len(set(word.lower() for word in words))
features['lexical_diversity'] = unique_words / len(words) if words else 0
# Punctuation patterns
punct_count = sum(1 for char in text if char in string.punctuation)
features['punctuation_ratio'] = punct_count / len(text) if text else 0
# Sentence structure
features['sentence_count'] = len(sentences)
if len(sentences) > 1:
sentence_lengths = [len(s.split()) for s in sentences]
features['sentence_length_variance'] = np.var(sentence_lengths)
else:
features['sentence_length_variance'] = 0
# Word frequency patterns
word_freq = Counter(word.lower() for word in words)
most_common_freq = word_freq.most_common(1)[0][1] if word_freq else 1
features['max_word_frequency'] = most_common_freq / len(words)
# Function words (common in AI text)
function_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'}
function_word_count = sum(1 for word in words if word.lower() in function_words)
features['function_word_ratio'] = function_word_count / len(words) if words else 0
# AI-specific patterns
ai_indicators = ['furthermore', 'moreover', 'additionally', 'consequently', 'therefore', 'thus', 'hence']
ai_indicator_count = sum(1 for word in words if word.lower() in ai_indicators)
features['ai_indicator_ratio'] = ai_indicator_count / len(words) if words else 0
# Repetition patterns
bigrams = [(words[i].lower(), words[i+1].lower()) for i in range(len(words)-1)]
unique_bigrams = len(set(bigrams))
features['bigram_diversity'] = unique_bigrams / len(bigrams) if bigrams else 0
return features
def calculate_perplexity_score(self, text: str) -> float:
"""Calculate a simplified perplexity-like score"""
if not self.model or not self.tokenizer:
words = text.split()
if len(words) < 5:
return 0.5
avg_word_length = np.mean([len(word) for word in words])
sentence_count = len(re.split(r'[.!?]+', text))
complexity_score = (avg_word_length * sentence_count) / len(words)
return min(max(complexity_score, 0.1), 0.9)
try:
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
with torch.no_grad():
outputs = self.model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)
confidence = torch.max(probs).item()
return 1.0 - confidence
except:
return 0.5
def detect_refinement_patterns(self, text: str, linguistic_features: Dict) -> Dict[str, float]:
"""Detect patterns indicating AI refinement/editing"""
refinement_indicators = {}
sentences = re.split(r'[.!?]+', text)
sentences = [s.strip() for s in sentences if s.strip()]
# Check for overly consistent sentence structure
if len(sentences) > 2:
lengths = [len(s.split()) for s in sentences]
length_consistency = 1.0 - (np.std(lengths) / np.mean(lengths)) if np.mean(lengths) > 0 else 0
refinement_indicators['structure_consistency'] = min(length_consistency, 1.0)
else:
refinement_indicators['structure_consistency'] = 0.5
# Check for formal language patterns
formal_words = ['furthermore', 'moreover', 'consequently', 'therefore', 'additionally', 'subsequently']
formal_count = sum(1 for word in text.lower().split() if word in formal_words)
refinement_indicators['formality_score'] = min(formal_count / len(text.split()) * 10, 1.0)
# Check for lack of contractions
contractions = ["n't", "'ll", "'re", "'ve", "'m", "'d", "'s"]
contraction_count = sum(1 for word in text.split() if any(cont in word for cont in contractions))
words_count = len(text.split())
refinement_indicators['contraction_absence'] = 1.0 - min(contraction_count / words_count * 5, 1.0) if words_count > 0 else 0.5
# Check for punctuation patterns
punct_perfect_score = 0.5
if ',' in text and '.' in text:
comma_count = text.count(',')
period_count = text.count('.')
if comma_count > 0 and period_count > 0:
punct_ratio = comma_count / (comma_count + period_count)
if 0.3 <= punct_ratio <= 0.7:
punct_perfect_score = 0.8
refinement_indicators['punctuation_perfection'] = punct_perfect_score
return refinement_indicators
def classify_text_category(self, text: str) -> Tuple[str, Dict[str, float], float]:
"""Classify text into 4 categories with confidence scores"""
if len(text.strip()) < 10:
return "Uncertain", {"ai_generated": 0.25, "ai_refined": 0.25, "human_ai_refined": 0.25, "human_written": 0.25}, 0.3
# Extract features
linguistic_features = self.extract_linguistic_features(text)
refinement_patterns = self.detect_refinement_patterns(text, linguistic_features)
perplexity_score = self.calculate_perplexity_score(text)
# Get transformer model prediction if available
transformer_ai_prob = 0.5
if self.model and self.tokenizer:
try:
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
with torch.no_grad():
outputs = self.model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)
transformer_ai_prob = probs[0][1].item()
except:
pass
# Calculate category probabilities
scores = {}
# AI-generated score
ai_generated_score = 0.0
if linguistic_features:
ai_generated_score = (
transformer_ai_prob * 0.4 +
(1.0 - linguistic_features.get('lexical_diversity', 0.5)) * 0.2 +
linguistic_features.get('ai_indicator_ratio', 0) * 0.15 +
(1.0 - linguistic_features.get('sentence_length_variance', 0.5) / 10) * 0.15 +
(1.0 - perplexity_score) * 0.1
)
else:
ai_generated_score = transformer_ai_prob
scores['ai_generated'] = min(max(ai_generated_score, 0.0), 1.0)
# AI-generated & AI-refined score
ai_refined_score = 0.0
if refinement_patterns:
ai_refined_score = (
transformer_ai_prob * 0.3 +
refinement_patterns.get('structure_consistency', 0) * 0.25 +
refinement_patterns.get('formality_score', 0) * 0.25 +
refinement_patterns.get('punctuation_perfection', 0) * 0.2
)
else:
ai_refined_score = transformer_ai_prob * 0.7
scores['ai_refined'] = min(max(ai_refined_score, 0.0), 1.0)
# Human-written & AI-refined score
human_ai_refined_score = 0.0
if linguistic_features and refinement_patterns:
human_ai_refined_score = (
(1.0 - transformer_ai_prob) * 0.3 +
linguistic_features.get('lexical_diversity', 0.5) * 0.2 +
refinement_patterns.get('structure_consistency', 0) * 0.2 +
refinement_patterns.get('contraction_absence', 0) * 0.15 +
refinement_patterns.get('formality_score', 0) * 0.15
)
else:
human_ai_refined_score = (1.0 - transformer_ai_prob) * 0.6
scores['human_ai_refined'] = min(max(human_ai_refined_score, 0.0), 1.0)
# Human-written score
human_written_score = 0.0
if linguistic_features:
human_written_score = (
(1.0 - transformer_ai_prob) * 0.4 +
linguistic_features.get('lexical_diversity', 0.5) * 0.2 +
linguistic_features.get('sentence_length_variance', 0.5) / 10 * 0.15 +
(1.0 - refinement_patterns.get('structure_consistency', 0.5)) * 0.15 +
perplexity_score * 0.1
)
else:
human_written_score = 1.0 - transformer_ai_prob
scores['human_written'] = min(max(human_written_score, 0.0), 1.0)
# Normalize scores
total_score = sum(scores.values())
if total_score > 0:
scores = {k: v / total_score for k, v in scores.items()}
else:
scores = {"ai_generated": 0.25, "ai_refined": 0.25, "human_ai_refined": 0.25, "human_written": 0.25}
# Determine primary category
primary_category = max(scores, key=scores.get)
confidence = scores[primary_category]
# Map to readable names
category_names = {
'ai_generated': 'AI-generated',
'ai_refined': 'AI-generated & AI-refined',
'human_ai_refined': 'Human-written & AI-refined',
'human_written': 'Human-written'
}
return category_names[primary_category], scores, confidence
def get_analysis_json(self, text: str) -> Dict:
"""Get analysis results in JSON format for API"""
start_time = time.time()
if not text or len(text.strip()) < 10:
return {
"error": "Text must be at least 10 characters long",
"ai_percentage": 0,
"human_percentage": 0,
"category_scores": {
"ai_generated": 0,
"ai_refined": 0,
"human_ai_refined": 0,
"human_written": 0
},
"primary_category": "uncertain",
"confidence": 0,
"processing_time_ms": 0
}
try:
primary_category, category_scores, confidence = self.classify_text_category(text)
ai_percentage = (category_scores['ai_generated'] + category_scores['ai_refined']) * 100
human_percentage = (category_scores['human_ai_refined'] + category_scores['human_written']) * 100
processing_time = (time.time() - start_time) * 1000
return {
"ai_percentage": round(ai_percentage, 1),
"human_percentage": round(human_percentage, 1),
"category_scores": {
"ai_generated": round(category_scores['ai_generated'] * 100, 1),
"ai_refined": round(category_scores['ai_refined'] * 100, 1),
"human_ai_refined": round(category_scores['human_ai_refined'] * 100, 1),
"human_written": round(category_scores['human_written'] * 100, 1)
},
"primary_category": primary_category.lower().replace(' ', '_').replace('-', '_'),
"confidence": round(confidence * 100, 1),
"processing_time_ms": round(processing_time, 1)
}
except Exception as e:
return {
"error": str(e),
"ai_percentage": 0,
"human_percentage": 0,
"category_scores": {
"ai_generated": 0,
"ai_refined": 0,
"human_ai_refined": 0,
"human_written": 0
},
"primary_category": "error",
"confidence": 0,
"processing_time_ms": 0
}
# Initialize detector
detector = ImprovedAIDetector()
def create_bar_chart(ai_percentage, human_percentage):
"""Create vertical bar chart showing AI vs Human percentages"""
fig = go.Figure(data=[
go.Bar(
x=['AI', 'Human'],
y=[ai_percentage, human_percentage],
marker=dict(
color=['#FF6B6B', '#4ECDC4'],
line=dict(color='rgba(0,0,0,0.3)', width=2)
),
text=[f'{ai_percentage:.0f}%', f'{human_percentage:.0f}%'],
textposition='auto',
textfont=dict(size=14, color='white', family='Arial Black'),
hovertemplate='%{x}
%{y:.1f}%
Sophisticated 4-category classification with enhanced accuracy and user-friendly results
Detects pure AI content, AI-refined text, and human writing with detailed breakdowns
Our AI detector estimates the likelihood that text was created or modified using AI tools. The percentage shows our system's confidence, but it's not a definitive judgment.
Combine AI detection results with manual review, contextual knowledge, and other verification methods. This tool should support—not replace—human judgment in content evaluation.