import gradio as gr import difflib import re def clean_and_tokenize(text): # Normalize whitespaces return re.findall(r'\b\w+\b|[^\w\s]', text) def calculate_drift_metrics(text_a, text_b): words_a = text_a.split() words_b = text_b.split() set_a = set(words_a) set_b = set(words_b) # Jaccard similarity union = len(set_a.union(set_b)) intersection = len(set_a.intersection(set_b)) jaccard_sim = (intersection / union) * 100 if union > 0 else 100.0 # SequenceMatcher matcher = difflib.SequenceMatcher(None, words_a, words_b) ratio = matcher.ratio() * 100 # Edits breakdown opcodes = matcher.get_opcodes() insertions = 0 deletions = 0 replacements = 0 for tag, i1, i2, j1, j2 in opcodes: if tag == 'insert': insertions += (j2 - j1) elif tag == 'delete': deletions += (i2 - i1) elif tag == 'replace': replacements += max(i2 - i1, j2 - j1) drift_percent = 100 - ratio return { "jaccard": round(jaccard_sim, 2), "similarity": round(ratio, 2), "drift": round(drift_percent, 2), "insertions": insertions, "deletions": deletions, "replacements": replacements, "words_a": len(words_a), "words_b": len(words_b) } def generate_word_diff(text_a, text_b): # Clean texts words_a = text_a.splitlines() words_b = text_b.splitlines() # We will generate a beautiful side-by-side or inline HTML diff # difflib.HtmlDiff is nice, but we want a highly premium custom HTML view diff = difflib.HtmlDiff(wrapcolumn=60) html_table = diff.make_table(words_a, words_b, context=True, numlines=3) # Style the table to fit our glowing dark theme styled_html = f"""
{html_table}
""" return styled_html def handle_compare(text_a, file_a, text_b, file_b): # Retrieve values final_a = text_a if file_a is not None: try: with open(file_a.name, 'r', encoding='utf-8', errors='ignore') as f: final_a = f.read() except Exception as e: final_a = f"Error reading File A: {str(e)}" final_b = text_b if file_b is not None: try: with open(file_b.name, 'r', encoding='utf-8', errors='ignore') as f: final_b = f.read() except Exception as e: final_b = f"Error reading File B: {str(e)}" if not final_a.strip() and not final_b.strip(): return "
Please enter or upload text for both versions to compare.
", "" # Calculate stats stats = calculate_drift_metrics(final_a, final_b) # HTML stats card stats_html = f"""
Document Similarity
{stats['similarity']}%
Lexical Drift Spread
{stats['drift']}%
Word Additions
+{stats['insertions']}
Word Deletions
-{stats['deletions']}

Provenance Analysis Summary

Version A contains {stats['words_a']} words, and Version B contains {stats['words_b']} words. There are {stats['replacements']} words modified in place. The vocabulary overlap index (Jaccard similarity) is {stats['jaccard']}%. This indicates a {"high" if stats['drift'] > 30 else "moderate" if stats['drift'] > 10 else "minimal"} degree of document mutation over successive drafts.

""" # Generate interactive table diff_table = generate_word_diff(final_a, final_b) return stats_html, diff_table # Setup CSS styling for premium look custom_css = """ body, .gradio-container { background-color: #0b0f19 !important; font-family: 'Inter', sans-serif !important; } h1, h2, h3 { color: #f8fafc !important; } .gr-button-primary { background: linear-gradient(135deg, #059669 0%, #10b981 100%) !important; border: none !important; } .gr-button-primary:hover { opacity: 0.9 !important; } """ # App Interface with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald", secondary_hue="slate"), css=custom_css) as demo: gr.HTML("""

📊 Version Diffing & Provenance Auditor

Audit the evolution of policies, legislative bills, manuscripts, or transcripts with statistical drift analytics.

""") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📜 Original Version (Draft A)") text_input_a = gr.Textbox(label="Paste Text A", placeholder="Enter original draft here...", lines=10) file_input_a = gr.File(label="Or Upload Text File A", file_types=[".txt"]) with gr.Column(scale=1): gr.Markdown("### 📝 New Version (Draft B)") text_input_b = gr.Textbox(label="Paste Text B", placeholder="Enter revised draft here...", lines=10) file_input_b = gr.File(label="Or Upload Text File B", file_types=[".txt"]) compare_btn = gr.Button("🔍 Audit Document Drift & Generate Diff Table", variant="primary") gr.Markdown("### 📈 Analytical Provenance Dashboard & Drift Metrics") stats_output = gr.HTML(value="
Submit documents above to calculate version changes.
") gr.Markdown("### 🔍 Color-Coded Structural Difference View") diff_output = gr.HTML(value="
Visual diff will render here. Green represents additions, red represents deletions, and yellow represents replacements.
") # Wiring events compare_btn.click( fn=handle_compare, inputs=[text_input_a, file_input_a, text_input_b, file_input_b], outputs=[stats_output, diff_output] ) if __name__ == "__main__": demo.launch()