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()