# cli.py (V2/V3 - Adım 3.1: Anlamsal Görselleştirme Entegrasyonu) import typer from rich.console import Console from rich.panel import Panel from rich.table import Table from rich.text import Text from rich.padding import Padding import data_models import nlp_utils import argument_analyzer import logic_analyzer # import evidence_analyzer # Kaldırıldı import rhetoric_analyzer import synthesis_engine import argument_visualizer # Güncellenmiş versiyonu çağıracak import networkx as nx from typing import Optional, List import sys import textwrap console = Console() sentences = [] # --- Yardımcı Raporlama Fonksiyonları (Aynı) --- def format_sentence_with_highlights( sentence_idx: int, all_sentences: List[data_models.SentenceInfo], findings: List[data_models.Finding], components: List[data_models.ArgumentComponent] ) -> Text: # ... (Fonksiyon içeriği öncekiyle aynı)... if sentence_idx < 0 or sentence_idx >= len(all_sentences): return Text("(Error: Invalid sentence index)") sentence = all_sentences[sentence_idx]; text = Text(sentence.text) for comp in components: if comp.sentence_index == sentence_idx: style = "bold magenta" if comp.component_type == "Claim" else "magenta" start = comp.span_start - sentence.start_char; end = comp.span_end - sentence.start_char if 0 <= start < end <= len(sentence.text): try: text.stylize(style, start, end) except Exception as e: console.print(f"[dim yellow]Warn: Styling component ({start}-{end}) in sent {sentence_idx}: {e}[/dim]") for finding in findings: if finding.span_start == sentence.start_char and finding.span_end == sentence.end_char: prefix = "[F] " if finding.finding_type == "Fallacy" else \ "[R] " if finding.finding_type == "RhetoricalDevice" else "[?] " style = "bold red" if prefix=="[F] " else "bold yellow" if prefix=="[R] " else "" text.insert(0, prefix, style=style) return text # Ana CLI Fonksiyonu def main( text: Optional[str] = typer.Option(None, "--text", "-t", help="Text to analyze directly."), file_path: Optional[str] = typer.Option(None, "--file", "-f", help="Path to a text file to analyze."), max_findings_display: int = typer.Option(5, "--max-findings", "-m", help="Max number of each finding type to display in detail.") ): """ ETHOS: The AI Arbiter of Rational Discourse (CLI) - v2.2 Semantic Argument Linking & AI Fallacy Integration. """ console.print(Panel("[bold cyan]ETHOS Analysis Engine v2.2 Starting...[/bold cyan]", expand=False, border_style="cyan")) # --- Girdi Kontrolü ve Yükleme --- if text and file_path: console.print("[bold red]Error: Use --text OR --file, not both.[/bold red]"); raise typer.Exit(code=1) if not text and not file_path: console.print("[bold red]Error: Use --text '...' OR --file '...'[/bold red]"); raise typer.Exit(code=1) text_to_analyze = ""; input_source_msg = "" if file_path: try: with open(file_path, 'r', encoding='utf-8') as f: text_to_analyze = f.read() input_source_msg = f"File: [yellow]'{file_path}'[/yellow]" except Exception as e: console.print(f"[bold red]Error reading file '{file_path}': {e}[/bold red]"); raise typer.Exit(code=1) elif text: text_to_analyze = text; input_source_msg = f"Input Text ({len(text_to_analyze)} chars)" if not text_to_analyze.strip(): console.print("[bold red]Error: Input text is empty.[/bold red]"); raise typer.Exit(code=1) console.print(Padding(f"Analyzing: {input_source_msg}", (0, 1))) # --- Analiz Adımları --- console.print("\n[bold blue]--- Initializing Analyzers & Embeddings ---[/bold blue]") try: nlp_utils.load_spacy_model(); nlp_utils.load_bert() spacy_doc = nlp_utils.process_text_spacy(text_to_analyze) # Tüm cümle embeddinglerini başta hesapla sentence_embeddings = nlp_utils.get_all_sentence_embeddings(spacy_doc) except Exception as e: console.print(f"[bold red]Error during model loading/processing/embedding: {e}[/bold red]"); raise typer.Exit(code=1) if not spacy_doc: console.print("[bold red]Error: spaCy doc creation failed.[/bold red]"); raise typer.Exit(code=1) # --- Analizleri Çalıştır --- console.print("\n[bold blue]--- Running Analysis Modules ---[/bold blue]") global sentences sentences = [data_models.SentenceInfo(text=s.text, start_char=s.start_char, end_char=s.end_char, tokens=[t.text for t in s]) for s in spacy_doc.sents] console.print("[cyan]Running Argument Analyzer (Enhanced)...[/cyan]") argument_components = argument_analyzer.enhanced_component_analyzer(spacy_doc) console.print("[cyan]Running Logic Analyzer (Enhanced - Rules + ML)...[/cyan]") fallacy_findings = logic_analyzer.enhanced_fallacy_analyzer(spacy_doc) evidence_findings = [] # Kanıt analizi kaldırıldı console.print("[cyan]Running Rhetoric Analyzer...[/cyan]") rhetoric_findings = rhetoric_analyzer.simple_rhetoric_analyzer(spacy_doc) console.print("[cyan]Running Synthesis Engine (Evidence Excluded)...[/cyan]") all_findings: List[data_models.Finding] = fallacy_findings + rhetoric_findings analysis_summary = synthesis_engine.generate_summary_ratings(argument_components, all_findings) # --- Argüman Grafiğini Oluştur (Embeddingler ile) --- console.print("[cyan]Building Argument Graph (Semantic Linking)...[/cyan]") # Görselleyiciye embedding listesini de gönder argument_graph = argument_visualizer.build_argument_graph(argument_components, sentence_embeddings) # <-- DEĞİŞİKLİK BURADA graph_text_representation = argument_visualizer.format_graph_text(argument_graph) # --- Sonuç Nesnesini Oluştur --- analysis_result = data_models.AnalyzedText( original_text=text_to_analyze, sentences=sentences, argument_components=argument_components, findings=all_findings, analysis_summary=analysis_summary ) # --- Raporlama --- console.rule("[bold green]ETHOS Analysis Report[/bold green]", style="green") # Bölüm 1: Özet (Aynı) summary_table = Table(title="Analysis Summary", show_header=False, box=None, padding=(0, 1)) # ... (Özet tablo kodu aynı) ... if analysis_result.analysis_summary: for category, rating in analysis_result.analysis_summary.items(): style = "red" if rating.startswith(("Low", "Weak", "Questionable")) else "yellow" if rating.startswith(("Medium", "Moderate", "Mixed")) else "green" if rating == "Not Evaluated": style = "dim" summary_table.add_row(category, f"[{style}]{rating}[/{style}]") else: summary_table.add_row("Summary", "[dim]Not generated.[/dim]") console.print(Padding(summary_table, (1, 0))) # Bölüm 2: Tespit Edilen Bulgular (Aynı) console.print("\n[bold underline]Detected Findings:[/bold underline]") if not analysis_result.findings: console.print(Padding(" No significant findings detected.", (0, 2))) else: # ... (Bulgu gruplama ve yazdırma kodu aynı) ... grouped_findings = {}; for f in analysis_result.findings: grouped_findings.setdefault(f.finding_type, []).append(f) grouped_findings.pop("EvidenceIndicator", None); grouped_findings.pop("EvidenceStatus", None) if not grouped_findings: console.print(Padding(" No significant findings detected.", (0, 2))) else: for f_type, findings_list in grouped_findings.items(): color = "red" if f_type=="Fallacy" else "yellow" if f_type=="RhetoricalDevice" else "white" console.print(Padding(f"[bold {color}]{f_type} Indicators ({len(findings_list)} found):[/bold {color}]", (1, 1))) for i, finding in enumerate(findings_list[:max_findings_display]): details_dict = finding.details if finding.details else {} details_text = details_dict.get('fallacy_type') or details_dict.get('device_type') or 'Details N/A' trigger_text = details_dict.get('trigger') or details_dict.get('words') confidence = details_dict.get('confidence'); model_used = details_dict.get('model_used') details_str = f"({details_text}" if trigger_text and not model_used: details_str += f", Trigger: '{textwrap.shorten(str(trigger_text), width=25, placeholder='...')}'" if confidence is not None: details_str += f", Score: {confidence:.2f}" if model_used: details_str += f", Model: '{model_used.split('/')[-1]}'" details_str += ")" console.print(Padding(f"{i+1}. {finding.description} {details_str}", (0, 3))) try: sentence_idx = next(idx for idx, s in enumerate(analysis_result.sentences) if s.start_char == finding.span_start) related_sentence_text = analysis_result.sentences[sentence_idx].text console.print(Padding(f"[dim] In Sent {sentence_idx+1}: \"{textwrap.shorten(related_sentence_text, width=90, placeholder='...')}\"[/dim]", (0, 5))) except StopIteration: console.print(Padding(f"[dim] (Could not pinpoint exact sentence for span starting at char {finding.span_start})[/dim]", (0,5))) if len(findings_list) > max_findings_display: console.print(Padding(f"... and {len(findings_list) - max_findings_display} more.", (0, 3))) # Bölüm 3: Argüman Bileşenleri (Aynı) console.print("\n[bold underline]Identified Argument Components:[/bold underline]") if not analysis_result.argument_components: console.print(Padding(" No argument components identified.", (0, 2))) else: # ... (Bileşen tablosu kodu aynı) ... comp_table = Table(title=None, show_header=True, header_style="bold magenta", box=None, padding=(0,1)) comp_table.add_column("Type", style="magenta", width=10); comp_table.add_column("Text Snippet (Confidence)") for comp in analysis_result.argument_components: conf_str = f"({comp.confidence:.2f})" if comp.confidence is not None else "" comp_table.add_row(comp.component_type, f"\"{textwrap.shorten(comp.text, width=90, placeholder='...')}\" {conf_str}") console.print(Padding(comp_table, (1, 1))) # Bölüm 4: Argüman Yapısı Görselleştirmesi (Güncellenmiş formatı yazdıracak) console.print("\n[bold underline]Argument Structure Visualization (Semantic):[/bold underline]") # Başlık güncellendi console.print(Padding(graph_text_representation, (0, 1))) # <-- GRAFİĞİ YAZDIR # Not metni güncellendi console.print(Padding(f"[dim](Note: Links based on semantic similarity >= {argument_visualizer.LINKING_SIMILARITY_THRESHOLD})[/dim]", (0,1))) console.rule(style="green") console.print(f"(V2 Semantic Argument Linking. Total potential findings: {len(analysis_result.findings)})") # Mesaj güncellendi # Script doğrudan çalıştırıldığında typer.run ile main fonksiyonunu çağır if __name__ == "__main__": typer.run(main)