Spaces:
Runtime error
Runtime error
| 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, "<div style='color: #ef4444;'>Please paste debate transcripts or editorials.</div>", "" | |
| 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'<span style="background-color: rgba(239, 68, 68, 0.25); border-bottom: 2px solid #ef4444; color: #f87171; padding: 2px 4px; border-radius: 4px; font-weight: 500;" title="{item["Fallacy Category"]}">{indicator}</span>' | |
| 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""" | |
| <style> | |
| .styled-table {{ width: 100%; border-collapse: collapse; font-family: 'Inter', sans-serif; font-size: 13px; color: #cbd5e1; background: #0f172a; border-radius: 8px; overflow: hidden; border: 1px solid #334155; }} | |
| .styled-table th {{ background: #1e293b; color: #94a3b8; font-weight: 600; padding: 10px; text-align: left; border-bottom: 2px solid #334155; }} | |
| .styled-table td {{ padding: 8px 10px; border-bottom: 1px solid #1e293b; }} | |
| .styled-table tr:hover td {{ background: #1e293b; }} | |
| </style> | |
| <div style="overflow-x: auto;"> | |
| {table_html} | |
| </div> | |
| """ | |
| else: | |
| warnings_html = "<div style='color: #10b981; font-weight: 600; padding: 1.5rem; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border: 1px solid #10b981; text-align: center;'>🎉 Clean Argument Scorecard! No major fallacious connectors detected in this text.</div>" | |
| highlighted_container = f""" | |
| <div style="background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; max-height: 400px; overflow-y: auto; color: #e2e8f0; line-height: 1.6; font-size: 14px;"> | |
| {highlighted_text} | |
| </div> | |
| """ | |
| 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(""" | |
| <div style="text-align: center; margin-bottom: 2rem; border-bottom: 1px solid #1e293b; padding-bottom: 1.5rem;"> | |
| <h1 style="font-size: 2.2rem; font-weight: 800; background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚖️ Argument Fallacy Mapper & Rhetoric Deconstructor</h1> | |
| <p style="color: #94a3b8; font-size: 1.1rem; margin-top: 0.5rem;">Scan speeches, debate transcripts, and editorials for logical fallacies and receive constructive reasoning suggestions.</p> | |
| </div> | |
| """) | |
| 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("<div style='color: #64748b;'>Submit text to analyze and highlight fallacious reasoning.</div>") | |
| gr.Markdown("### ⚡ Deconstructed Logic & Constructive Recommendations") | |
| warnings_output = gr.HTML("<div style='color: #64748b;'>Scorecard grid will render here.</div>") | |
| audit_btn.click( | |
| fn=deconstruct_rhetoric, | |
| inputs=[text_input], | |
| outputs=[chart_output, highlighted_output, warnings_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |