# ========================================== # (PARTE 1: Importación de librerías esenciales) # ========================================== import gradio as gr import torch import math import re import spaces import pypdf import docx import os from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModelForCausalLM # ========================================== # (PARTE 2: Carga de Modelos Públicos Abiertos) # ========================================== # Modelos 100% públicos sin restricción de Token/Auth MODEL_DETECTOR = "distilbert-base-multilingual-cased" MODEL_HUMANIZADOR = "DeepESP/gpt2-spanish" tokenizer_det = AutoTokenizer.from_pretrained(MODEL_DETECTOR) model_det = AutoModelForSequenceClassification.from_pretrained(MODEL_DETECTOR, num_labels=2) tokenizer_hum = AutoTokenizer.from_pretrained(MODEL_HUMANIZADOR) model_hum = AutoModelForCausalLM.from_pretrained(MODEL_HUMANIZADOR) # ========================================== # (PARTE 3: Procesamiento y Filtro de Metadatos) -> Exclusión de datos académicos y preparación de texto # ========================================== def es_metadato_o_encabezado(texto): patrones_metadatos = [ r'\bnombre\b', r'\bdocente\b', r'\bfecha\b', r'\basignatura\b', r'\bnrc\b', r'\bactividad\b', r'\buniversidad\b', r'\bcarrera\b', r'\b\d{1,2}/\d{1,2}/\d{2,4}\b' ] texto_lower = texto.lower() palabras_clave = sum(1 for p in patrones_metadatos if re.search(p, texto_lower)) if palabras_clave >= 2 or (len(texto.split()) < 6 and palabras_clave >= 1): return True return False def extraer_texto_documento(archivo): if archivo is None: return "" texto = "" ruta = archivo.name ext = os.path.splitext(ruta)[1].lower() try: if ext == '.pdf': reader = pypdf.PdfReader(ruta) for page in reader.pages: texto += page.extract_text() + "\n" elif ext == '.docx': doc = docx.Document(ruta) for para in doc.paragraphs: texto += para.text + "\n" else: with open(ruta, 'r', encoding='utf-8', errors='ignore') as f: texto = f.read() except Exception as e: return f"[Error al extraer texto]: {str(e)}" texto_limpio = re.sub(r'(? Análisis Fiel y Reescritura Limpia # ========================================== @spaces.GPU def ejecutar_analisis(texto_manual, archivo_subido): texto_base = extraer_texto_documento(archivo_subido) if archivo_subido else texto_manual if not texto_base or not texto_base.strip(): return texto_base, "

Error: No hay texto para analizar.

", "0% IA", "Esperando datos..." oraciones = [s.strip() + p for s, p in zip(re.split(r'[.!?\n]+', texto_base), re.findall(r'[.!?\n]+', texto_base) + [''])] oraciones = [o for o in oraciones if o.strip()] if not oraciones: oraciones = [texto_base] html_visor = "
" puntajes_totales = [] for oracion in oraciones: if es_metadato_o_encabezado(oracion): html_visor += f"{oracion} " continue score = calcular_probabilidad_ia_espanol(oracion) puntajes_totales.append(score) if score > 65: clase, tooltip = "high-ai", "Alta probabilidad de IA" elif score > 40: clase, tooltip = "medium-ai", "Posible intervención de IA" else: clase, tooltip = "low-ai", "Redacción Humana" html_visor += f"{oracion} " html_visor += "
" promedio_ia = sum(puntajes_totales) / len(puntajes_totales) if puntajes_totales else 0 indicador = f"{round(promedio_ia, 1)}% IA" estado = "Análisis completado mediante clasificador especializado en Español." return texto_base, html_visor, indicador, estado @spaces.GPU def ejecutar_humanizacion(texto_base): if not texto_base or not texto_base.strip(): return "Primero debe ingresar y analizar un texto." try: lineas = texto_base.split('\n') lineas_cuerpo = [l for l in lineas if not es_metadato_o_encabezado(l)] texto_filtrado = " ".join(lineas_cuerpo) if lineas_cuerpo else texto_base # Prompt ajustado para evitar la generación de comillas y mantener la ortografía exacta en español prompt = f"Paraphrase the following Spanish text into natural, formal, and grammatically correct Spanish without adding quotation marks or removing words: {texto_filtrado}" inputs = tokenizer_hum(prompt, return_tensors="pt", max_length=1024, truncation=True) outputs = model_hum.generate( inputs.input_ids, max_length=1024, temperature=0.3, # Reducido para evitar alucinaciones y faltas de ortografía top_p=0.9, do_sample=True, repetition_penalty=1.1 ) texto_humanizado = tokenizer_hum.decode(outputs[0], skip_special_tokens=True) # Limpieza de comillas accidentales generadas por el modelo texto_humanizado = texto_humanizado.replace('"', '').replace("'", '').strip() return texto_humanizado except Exception as e: return f"Error en la humanización: {str(e)}" # ========================================== # (PARTE 5: Estilos CSS) -> Diseño de la Interfaz # ========================================== custom_css = """ body, .gradio-container { background-color: #0b0f19 !important; color: #e2e8f0 !important; font-family: 'Inter', system-ui, sans-serif !important; } .title-header { text-align: center; color: #0ea5e9; text-shadow: 0 0 15px rgba(14, 165, 233, 0.6); font-weight: 800; margin-bottom: 20px; } .document-viewer { background-color: #1e293b; padding: 20px; border-radius: 8px; border: 1px solid #334155; font-size: 15px; line-height: 1.8; max-height: 400px; overflow-y: auto; color: #f8fafc; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); } .high-ai { background-color: rgba(239, 68, 68, 0.4); border-bottom: 2px solid #ef4444; border-radius: 3px; } .medium-ai { background-color: rgba(245, 158, 11, 0.4); border-bottom: 2px solid #f59e0b; border-radius: 3px; } .low-ai { background-color: rgba(34, 197, 94, 0.2); border-bottom: 2px solid #22c55e; border-radius: 3px; } button.primary { background: linear-gradient(135deg, #0284c7 0%, #2563eb 100%) !important; border: none !important; box-shadow: 0 0 15px rgba(37, 99, 235, 0.5) !important; transition: 0.3s ease !important; } button.primary:hover { transform: translateY(-2px); box-shadow: 0 0 25px rgba(56, 189, 248, 0.8) !important; } button.secondary { border: 1px solid #0ea5e9 !important; color: #0ea5e9 !important; background: transparent !important; } """ # ========================================== # (PARTE 6: Interfaz Gráfica Gradio) # ========================================== with gr.Blocks(title="CVision AI Detector & Humanizer", css=custom_css) as demo: gr.Markdown( """ # CVision Advanced AI Detector & Humanizer ### Plataforma de Análisis Térmico de Documentos y Reescritura Cognitiva """, elem_classes=["title-header"] ) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 1. Ingrese el Contenido") with gr.Tabs(): with gr.TabItem("Texto Directo"): input_text = gr.Textbox(label="Texto a analizar", lines=8, placeholder="Pegue su texto aquí...") with gr.TabItem("Subir Documento"): input_file = gr.File(label="Cargar archivo (.docx, .pdf)", file_types=[".pdf", ".docx"]) texto_interno = gr.Textbox(visible=False) btn_analizar = gr.Button("🔍 Ejecutar Análisis de IA", variant="primary") with gr.Column(scale=1): gr.Markdown("### 2. Visor de Análisis Térmico") output_html = gr.HTML(value="


El documento analizado aparecerá aquí con resaltado de IA.
Rojo: Alta IA | Amarillo: Dudoso | Verde: Humano
") with gr.Row(): indicador_ia = gr.Label(label="Probabilidad Global de IA") estado_sis = gr.Textbox(label="Registro del Sistema", interactive=False) gr.HTML("
") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 3. Módulo de Humanización") gr.Markdown("Si el análisis detecta un alto porcentaje de IA, utilice el motor de reescritura para naturalizar el contenido seleccionado.") btn_humanizar = gr.Button("🧬 Humanizar Texto Detectado", variant="secondary") with gr.Column(scale=2): output_humanizado = gr.Textbox(label="Documento Humanizado (Resultado Final)", lines=6, interactive=True) btn_analizar.click( fn=ejecutar_analisis, inputs=[input_text, input_file], outputs=[texto_interno, output_html, indicador_ia, estado_sis] ) btn_humanizar.click( fn=ejecutar_humanizacion, inputs=[texto_interno], outputs=[output_humanizado] ) # ========================================== # (PARTE 7: Despliegue de la Aplicación) # ========================================== demo.launch()