import streamlit as st import os import re from inference import predict from download_report import generate_report_pdf from utils import compute_all_linguistic_features, text_input_generate, generate_explanation_with_gemma # st.title("AI vs Human text Classifier", anchor=False) st.set_page_config(page_title="Report Generator", layout="wide", initial_sidebar_state="collapsed") st.markdown("

AI vs Human text Classifier

", unsafe_allow_html=True) st.divider() MAX_WORDS = 1000 def on_explanation_toggle(): """Callback when explanation toggle is changed.""" if st.session_state.show_explanation: st.session_state.selected_model = "nn_model_bert" def enforce_word_limit(): text = st.session_state.get("input_text", "") word_count = len(text.split()) if word_count > MAX_WORDS: # Truncate to max words words = text.split() st.session_state.input_text = " ".join(words[:MAX_WORDS]) def extract_text_from_file(uploaded_file): """Extract text from an uploaded .doc, .docx, or .pdf file.""" file_extension = os.path.splitext(uploaded_file.name)[1].lower() text = "" if file_extension == ".pdf": try: import PyPDF2 pdf_reader = PyPDF2.PdfReader(uploaded_file) for page in pdf_reader.pages: text += page.extract_text() + "\n" except ImportError: st.error("PyPDF2 is required to read PDF files. Install it with: pip install PyPDF2") return "" elif file_extension == ".docx": try: import docx doc = docx.Document(uploaded_file) text = "\n".join([paragraph.text for paragraph in doc.paragraphs]) except ImportError: st.error("python-docx is required to read Word files. Install it with: pip install python-docx") return "" # Limit to MAX_WORDS words = text.split() if len(words) > MAX_WORDS: text = " ".join(words[:MAX_WORDS]) return text def on_file_upload(): """Callback when a file is uploaded. Populates the text area with extracted content.""" uploaded_file = st.session_state.get("file_uploader") if uploaded_file is not None: extracted_text = extract_text_from_file(uploaded_file) if extracted_text: st.session_state.input_text = extracted_text def generate_text_stats(input_text): """Generate statistics for the input text.""" words = input_text.split() word_count = len(words) # Sentence length distribution (split by . ! ?) sentences = re.split(r'[.!?]+', input_text) sentences = [s.strip() for s in sentences if s.strip()] sentence_lengths = [len(s.split()) for s in sentences] stats_lines = [f"**Word Count:** {word_count}"] stats_lines.append("") stats_lines.append("**Sentence Length Distribution:**") if sentence_lengths: avg_sent_len = sum(sentence_lengths) / len(sentence_lengths) stats_lines.append(f"- Number of sentences: {len(sentences)}") stats_lines.append(f"- Average sentence length: {avg_sent_len:.1f} words") stats_lines.append(f"- Shortest sentence: {min(sentence_lengths)} words") stats_lines.append(f"- Longest sentence: {max(sentence_lengths)} words") stats_lines.append("") stats_lines.append("| Sentence # | Word Count |") stats_lines.append("|------------|------------|") for i, slen in enumerate(sentence_lengths, 1): stats_lines.append(f"| {i} | {slen} |") else: stats_lines.append("- No sentences detected.") return "\n".join(stats_lines) def generate_prediction_response(input_text, selected_model): """Run inference and format the response.""" # Load model (already cached in inference.py) label, proba = predict(input_text, selected_model) label_str = "AI-generated" if label == 1 else "Human-written" confidence = proba if label == 1 else 1 - proba lines = [f"**Model:** {selected_model}"] lines.append("") lines.append(f"**Prediction:** {label_str}") lines.append(f"**Confidence:** {confidence:.4f} ({confidence:.2%})") return "\n".join(lines) def generate_linguistic_features_text(input_text): """Generate linguistic features report.""" features = compute_all_linguistic_features(input_text) lines = ["**Linguistic Features Analysis:**", ""] lines.append(f"**Burstiness:** {features['burstiness']:.4f}") lines.append(f"- Measures irregular word repetition patterns") lines.append(f"- Higher values indicate more bursty (human-like) writing") lines.append("") lines.append(f"**TTR (Type-Token Ratio):** {features['TTR']:.4f}") lines.append(f"- Ratio of unique words to total words") lines.append(f"- Higher values indicate richer vocabulary") lines.append("") lines.append(f"**CV (Coefficient of Variation of Sentence Length):** {features['CV_sentence_len']:.4f}") lines.append(f"- Standard deviation divided by mean of sentence lengths") lines.append(f"- Higher values indicate more variation in sentence structure") lines.append("") lines.append(f"**Perplexity:** {features['perplexity']:.4f}") lines.append(f"- How 'surprised' GPT-2 is by the text") lines.append(f"- Lower values indicate more predictable (likely AI-generated) text") return "\n".join(lines) left_col, right_col = st.columns([0.38, 0.62], gap="large") with left_col: # st.markdown('
Title
', unsafe_allow_html=True) st.text("Paste your text here or Upload a file") st.text_area("", value="", height=150, placeholder="text field", label_visibility="collapsed", key="input_text", on_change=enforce_word_limit) word_count = len(st.session_state.get("input_text", "").split()) if word_count > 0: if word_count > MAX_WORDS: st.warning(f"Word limit exceeded! Maximum {MAX_WORDS} words allowed. (Current: {word_count})") else: st.caption(f"Word count: {word_count}/{MAX_WORDS}") st.markdown("

OR

", unsafe_allow_html=True) st.file_uploader("Upload a file (.docx, .pdf)", type=["docx", "pdf"], accept_multiple_files=False, label_visibility="collapsed", key="file_uploader", on_change=on_file_upload) st.divider() model_col, dropdown_col = st.columns([0.8, 0.2]) with model_col: model_files = [os.path.splitext(f)[0] for f in os.listdir("models") if f.endswith((".pkl", ".h5", ".pt")) and os.path.splitext(f)[0] != "tfidf_vectorizer"] model_options = ["pick model here"] + sorted(model_files) + ["all models for comparison"] st.selectbox("", model_options, label_visibility="collapsed", key="selected_model") show_explanation = st.toggle("Explanation on", key="show_explanation", value=False, on_change=on_explanation_toggle) if show_explanation: st.info("Since explanation is on, we will be using nn_model_bert. Click Generate report.") st.divider() if st.button("Generate report", use_container_width=False, type="primary"): input_text = st.session_state.get("input_text", "").strip() selected_model = st.session_state.get("selected_model", "pick model here") show_explanation = st.session_state.get("show_explanation", False) if not input_text: st.warning("Please enter some text or upload a file first.") else: # Generate text statistics stats_text = generate_text_stats(input_text) # Generate prediction response if selected_model == "pick model here": response_text = "**No model selected.** Please choose a model from the dropdown." elif selected_model == "all models for comparison": # Run all models and compare (exclude tfidf_vectorizer since it's not a classifier) model_names = [m for m in model_files if m != "tfidf_vectorizer"] response_lines = ["**All Models Comparison:**", ""] response_lines.append("| Model | Prediction | Confidence |") response_lines.append("|-------|------------|------------|") for mname in model_names: try: label, proba = predict(input_text, mname) label_str = "AI" if label == 1 else "Human" conf = proba if label == 1 else 1 - proba response_lines.append(f"| {mname} | {label_str} | {conf:.2%} |") except Exception as e: response_lines.append(f"| {mname} | Error | {str(e)} |") response_text = "\n".join(response_lines) else: response_text = generate_prediction_response(input_text, selected_model) # Generate linguistic features if explanation is on if show_explanation: linguistic_text = generate_linguistic_features_text(input_text) features = compute_all_linguistic_features(input_text) # Get prediction details if selected_model != "pick model here" and selected_model != "all models for comparison": label, proba = predict(input_text, selected_model) label_str = "AI-generated" if label == 1 else "Human-written" confidence = proba if label == 1 else 1 - proba # Generate combined text for later use (not shown to user) combined_text = text_input_generate(input_text, label_str, confidence, features) st.session_state.combined_text_input = combined_text # Generate explanation using Gemma model with st.spinner("Generating explanation with Gemma model..."): explanation = generate_explanation_with_gemma(combined_text) st.session_state.gemma_explanation = explanation st.session_state.linguistic_features = linguistic_text st.session_state.report_stats = stats_text st.session_state.report_response = response_text with right_col: text_stats = st.container() response = st.container() linguistic_features = st.container() gemma_explanation_container = st.container() show_explanation = st.session_state.get("show_explanation", False) has_report = bool(st.session_state.get("report_stats")) and bool(st.session_state.get("report_response")) download_left, download_right = st.columns([0.64, 0.36]) with download_right: if has_report: st.download_button( "Download report", data=generate_report_pdf( st.session_state.report_stats, st.session_state.report_response, ), file_name="report.pdf", mime="application/pdf", use_container_width=True, ) else: st.button("Download report", disabled=True, use_container_width=True) show_explanation = st.session_state.get("show_explanation", False) with text_stats: report_stats = st.session_state.get("report_stats", "") if report_stats and show_explanation: st.markdown(report_stats) elif not show_explanation: st.markdown("*Explanations are turned off.*") else: st.markdown("*No statistics yet.*") with response: report_response = st.session_state.get("report_response", "") if report_response and show_explanation: st.markdown(report_response) elif not show_explanation: st.markdown('*Explanations are turned off.*') else: st.markdown('*No response yet.*') with linguistic_features: linguistic_text = st.session_state.get("linguistic_features", "") if linguistic_text and show_explanation: st.divider() st.markdown(linguistic_text) elif not show_explanation: st.divider() st.markdown('*Linguistic features are turned off.*') with gemma_explanation_container: gemma_explanation = st.session_state.get("gemma_explanation", "") if gemma_explanation and show_explanation: st.divider() st.subheader("AI Explanation") st.markdown(gemma_explanation) st.markdown("", unsafe_allow_html=True)