import gradio as gr
import pandas as pd
import numpy as np
import plotly.express as px
import re
# Fallacy Lexicon containing keywords, regex, descriptions, and plain-English corrections
FALLACY_PATTERNS = [
{
"name": "Ad Hominem (Personal Attack)",
"patterns": [
r'\b(idiot|fool|stupid|corrupt|liar|cheat|incompetent|naive|hypocrite|disgrace)\b',
r'\b(you are|he is|she is) (unqualified|irrational|delusional)\b'
],
"description": "Attacking the character or motives of a person rather than addressing their actual argument.",
"correction": "Focus on the logic, evidence, or specific data points of the opponent's claim, not their character."
},
{
"name": "Slippery Slope (Extreme Projections)",
"patterns": [
r'\b(inevitably lead to|slippery slope|disaster will follow|will cause the end of|road to ruin|downhill from here|avalanche of)\b',
r'\bif we allow.*then everything.*will collapse\b'
],
"description": "Claiming that a relatively small initial step will inevitably lead to a chain of disastrous events without supporting evidence.",
"correction": "Provide empirical evidence showing a causal mechanism connecting the initial step to the final extreme outcome."
},
{
"name": "False Dilemma (Either/Or)",
"patterns": [
r'\b(either we|either you|must choose between|no other choice|only two options|with us or against us|black or white)\b'
],
"description": "Presenting only two alternative states or choices when in fact multiple middle grounds or other options exist.",
"correction": "Acknowledge third-party options, compromise zones, or other collaborative solutions."
},
{
"name": "Appeal to False Authority",
"patterns": [
r'\b(everyone knows|scientists agree that|as.*said once|common knowledge|unnamed sources say|trust me on this)\b'
],
"description": "Claiming a statement must be true because an authority figure or 'everyone' asserts it, without citing verified evidence.",
"correction": "Cite direct peer-reviewed academic studies, primary documents, or transparent data collections."
},
{
"name": "Circular Reasoning (Begging the Question)",
"patterns": [
r'\b(because it is|obviously true since|by definition|self-evident|proved by the fact that it is)\b',
r'\b(.*) is true because (\1) is true\b'
],
"description": "An argument where the conclusion is already assumed in the premise, repeating the claim in different words.",
"correction": "Introduce independent external facts, logic, or observations to support the premise."
},
{
"name": "Strawman (Caricaturing Opponent)",
"patterns": [
r'\b(they want to destroy|their extreme goal|total ban on|completely wipe out|total elimination of|radical agenda to)\b'
],
"description": "Oversimplifying, exaggerating, or misrepresenting an opponent's argument to make it easier to attack.",
"correction": "State the opponent's argument in its strongest form (steelman) before attempting to refute it."
}
]
def deconstruct_rhetoric(text):
if not text.strip():
return None, "
Please paste debate transcripts or editorials.
", ""
text_lower = text.lower()
highlighted_text = text
fallacies_found = []
# Track statistics
summary_counts = {item["name"]: 0 for item in FALLACY_PATTERNS}
# Process each pattern
for fallacy in FALLACY_PATTERNS:
name = fallacy["name"]
desc = fallacy["description"]
corr = fallacy["correction"]
for pat in fallacy["patterns"]:
matches = list(re.finditer(pat, text_lower, re.IGNORECASE))
if matches:
summary_counts[name] += len(matches)
for m in matches:
matched_str = m.group(0)
fallacies_found.append({
"Rhetorical Indicator": matched_str,
"Fallacy Category": name,
"Logic Description": desc,
"Constructive Correction": corr
})
# Generate highlighted HTML (using span wraps)
# To prevent nested replacement bugs, we replace with placeholders and substitute at the end
replacements = {}
for idx, item in enumerate(fallacies_found):
indicator = item["Rhetorical Indicator"]
placeholder = f"___PLACEHOLDER_{idx}___"
# Case insensitive substitution (first occurrence)
pattern = re.compile(r'\b' + re.escape(indicator) + r'\b', re.IGNORECASE)
highlighted_text, count = pattern.subn(placeholder, highlighted_text, 1)
if count > 0:
replacements[placeholder] = f'{indicator}'
for placeholder, span in replacements.items():
highlighted_text = highlighted_text.replace(placeholder, span)
# Generate stats Plotly Chart
df_counts = pd.DataFrame({
"Fallacy Category": list(summary_counts.keys()),
"Count": list(summary_counts.values())
})
fig = px.bar(
df_counts[df_counts["Count"] > 0],
x="Count",
y="Fallacy Category",
orientation="h",
color="Fallacy Category",
template="plotly_dark",
title="Detected Rhetorical Fallacy Counts"
)
fig.update_layout(showlegend=False, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font_family="Inter")
# Generate deconstructed warnings table
warnings_html = ""
if fallacies_found:
df_fallacies = pd.DataFrame(fallacies_found)
table_html = df_fallacies.to_html(classes="styled-table", index=False)
warnings_html = f"""
{table_html}
"""
else:
warnings_html = "🎉 Clean Argument Scorecard! No major fallacious connectors detected in this text.
"
highlighted_container = f"""
{highlighted_text}
"""
return fig, highlighted_container, warnings_html
# Premium styling
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, #ef4444 0%, #b91c1c 100%) !important; border: none !important; }
"""
with gr.Blocks(theme=gr.themes.Soft(primary_hue="red", secondary_hue="slate"), css=custom_css) as demo:
gr.HTML("""
⚖️ Argument Fallacy Mapper & Rhetoric Deconstructor
Scan speeches, debate transcripts, and editorials for logical fallacies and receive constructive reasoning suggestions.
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📜 Paste Speech / Debate Transcript")
text_input = gr.Textbox(
label="Argument Text",
placeholder="Paste speech copy here...",
lines=12
)
audit_btn = gr.Button("🔍 Deconstruct Arguments", variant="primary")
with gr.Column(scale=1):
gr.Markdown("### 📊 Fallacy Incidence Breakdown")
chart_output = gr.Plot()
gr.Markdown("### 🎨 Annotated Rhetorical Passage Visualizer")
highlighted_output = gr.HTML("Submit text to analyze and highlight fallacious reasoning.
")
gr.Markdown("### ⚡ Deconstructed Logic & Constructive Recommendations")
warnings_output = gr.HTML("Scorecard grid will render here.
")
audit_btn.click(
fn=deconstruct_rhetoric,
inputs=[text_input],
outputs=[chart_output, highlighted_output, warnings_output]
)
if __name__ == "__main__":
demo.launch()