import os import gradio as gr import pandas as pd import numpy as np import re import plotly.express as px from huggingface_hub import InferenceClient def load_data(file_obj): """Safely loads CSV, Excel, or TXT file into a Pandas DataFrame.""" if file_obj is None: return None, gr.update(choices=[], visible=False), "Please upload a file." file_path = file_obj.name ext = os.path.splitext(file_path)[1].lower() try: if ext == '.csv': df = pd.read_csv(file_path) elif ext in ['.xls', '.xlsx']: df = pd.read_excel(file_path) elif ext == '.txt': with open(file_path, 'r', encoding='utf-8') as f: content = f.read() df = pd.DataFrame({'text': [content]}) else: return None, gr.update(choices=[], visible=False), "Unsupported file format. Please upload .csv, .xlsx, or .txt." string_cols = [col for col in df.columns if df[col].dtype == 'object' or df[col].astype(str).str.len().mean() > 5] if not string_cols: string_cols = list(df.columns) return df, gr.update(choices=string_cols, value=string_cols[0], visible=True), f"Successfully loaded dataset with {len(df)} rows." except Exception as e: return None, gr.update(choices=[], visible=False), f"Error loading file: {str(e)}" # A rigorous dictionary of English rhetorical discourse markers RHETORICAL_MARKERS = { "Contrast (Counterargument)": [ "however", "but", "yet", "nevertheless", "nonetheless", "on the other hand", "although", "though", "even though", "conversely", "meanwhile", "in contrast", "instead", "whereas", "despite", "in spite of", "alternatively" ], "Causation (Cause/Effect)": [ "because", "therefore", "since", "consequently", "as a result", "thus", "so", "hence", "accordingly", "because of", "due to", "leads to", "thereby", "for this reason", "so that", "if" ], "Addition (Elaboration)": [ "furthermore", "in addition", "moreover", "besides", "also", "additionally", "further", "not only", "firstly", "secondly", "finally", "next", "what is more", "indeed", "similarly", "likewise", "for example", "for instance" ], "Conclusion (Synthesis)": [ "overall", "to conclude", "in conclusion", "summarize", "in summary", "ultimately", "essentially", "in short", "all in all", "briefly", "concluding" ] } def get_highlighted_tokens(text, matches): """Helper to highlight recognized discourse connectors in Gradio.""" # matches: list of dicts: {"start": int, "end": int, "label": str} matches = sorted(matches, key=lambda x: x["start"]) highlighted = [] last_idx = 0 for m in matches: start, end, label = m["start"], m["end"], m["label"] if start < last_idx: continue if start > last_idx: highlighted.append((text[last_idx:start], None)) highlighted.append((text[start:end], label)) last_idx = end if last_idx < len(text): highlighted.append((text[last_idx:], None)) return highlighted def run_local_discourse(text): """Rule-based local parser extracting exact discourse markers and categorizing rhetorical moves.""" matches = [] # We iterate over every connector and find matches using boundaries for category, markers in RHETORICAL_MARKERS.items(): for marker in markers: # We match markers as exact words/phrases pattern = re.compile(r'\b' + re.escape(marker) + r'\b', re.IGNORECASE) for m in pattern.finditer(text): matches.append({ "start": m.start(), "end": m.end(), "marker": m.group(), "label": category }) # Sort matches and filter out overlapping indexes matches = sorted(matches, key=lambda x: x["start"]) clean_matches = [] last_end = 0 for m in matches: if m["start"] >= last_end: clean_matches.append(m) last_end = m["end"] # Format to table results = [] sentences = re.split(r'(?<=[.!?])\s+', text) for m in clean_matches: # Find which sentence contains this match for context marker_context = "" for sent in sentences: if m["marker"] in sent: marker_context = sent.strip() break results.append({ "Connector": m["marker"], "Rhetorical Category": m["label"], "Context Sentence": marker_context }) df_res = pd.DataFrame(results) # Format highlighted text highlighted = get_highlighted_tokens(text, [{"start": m["start"], "end": m["end"], "label": m["label"]} for m in clean_matches]) return df_res, highlighted def run_neural_discourse(text, hf_token, model_name): """Uses advanced generative instruction models to extract claim, evidence, and fallacy trees.""" if not hf_token: raise ValueError("Hugging Face API Token is required for Transformers mode.") client = InferenceClient(token=hf_token) prompt = f"""[INST] Analyze the argument structure, rhetorical patterns, and reasoning flow of this persuasive text. Identify the main CLAIM, the key pieces of EVIDENCE/premises, and list any LOGICAL FALLACIES detected. Keep the analysis highly structured, bulleted, and professional. Text to analyze: "{text}" [/INST]""" try: response = client.text_generation( prompt, model=model_name, max_new_tokens=600, temperature=0.3 ) return response except Exception as e: raise RuntimeError(f"Hugging Face API error: {str(e)}") def analyze_discourse(text_input, file_obj, text_col, method, hf_token, hf_model): docs = [] if file_obj is not None: df, _, _ = load_data(file_obj) if df is not None and text_col in df.columns: docs = df[text_col].astype(str).fillna("").tolist() elif text_input and text_input.strip(): docs = [text_input] if not docs: return None, None, None, "Please enter text or upload a valid dataset first." try: if method == "Local Cue-Based (CPU & Fast)": df_res, highlighted = run_local_discourse(docs[0]) if df_res.empty: return ( [("No rhetorical connectors detected in the text.", None)], pd.DataFrame(), None, "Finished analysis: No standard discourse markers were detected." ) # Plotly Pie Chart counts = df_res["Rhetorical Category"].value_counts().reset_index() counts.columns = ["Rhetorical Category", "Count"] fig = px.pie( counts, values="Count", names="Rhetorical Category", color="Rhetorical Category", title="Distribution of Rhetorical Moves", template="plotly_dark", color_discrete_sequence=px.colors.qualitative.Pastel ) fig.update_layout(height=350, margin=dict(l=20, r=20, t=40, b=20)) csv_path = "discourse_connectors_report.csv" df_res.to_csv(csv_path, index=False) return highlighted, df_res, fig, f"Analysis complete: Extracted **{len(df_res)}** rhetorical connectives." else: # Neural Mode raw_analysis = run_neural_discourse(docs[0], hf_token, hf_model) # Format neural output as text markdown # Return dummy table and chart for compatibility return [("See the Argument Tree & Logical Fallacies report in the text output.", None)], pd.DataFrame(), None, raw_analysis except Exception as e: return None, None, None, f"Execution failed: {str(e)}" custom_css = """ body { background-color: #0b0f19; color: #f3f4f6; } .gradio-container { font-family: 'Inter', sans-serif !important; } h1, h2 { color: #6366f1 !important; } """ with gr.Blocks(theme=gr.themes.Default(primary_hue="indigo", secondary_hue="slate"), css=custom_css) as demo: df_state = gr.State() gr.HTML("""
Deconstruct argument structures, track rhetorical connector networks, and detect logical fallacies. Evaluate local transition cues or unlock deep AI semantic trees using your personal Hugging Face Token.