Spaces:
Running on Zero
Running on Zero
Download app.py from CVision-assitant/CVision_AI_Detector: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/CVision-assitant/CVision_AI_Detector/resolve/main/app.py
- Command line
-
hf download hf://spaces/CVision-assitant/CVision_AI_Detector/app.py
-
curl -L -o app.py https://huggingface.co/spaces/CVision-assitant/CVision_AI_Detector/resolve/main/app.py
10.5 kB
| # ========================================== | |
| # (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'(?<!\n)\n(?!\n)', ' ', texto) | |
| return texto_limpio.strip() | |
| def calcular_probabilidad_ia_espanol(oracion): | |
| # Evaluaci贸n por clasificaci贸n probabil铆stica en espa帽ol (RoBERTa) | |
| if len(oracion.split()) < 5: | |
| return 20.0 # Oraciones muy cortas se consideran neutras/humanas | |
| inputs = tokenizer_det(oracion, return_tensors="pt", truncation=True, max_length=512) | |
| with torch.no_grad(): | |
| outputs = model_det(**inputs) | |
| probs = torch.nn.functional.softmax(outputs.logits, dim=-1) | |
| # La clase 1 en este modelo corresponde a contenido generado por IA | |
| prob_ia = probs[0][1].item() * 100 | |
| return prob_ia | |
| # ========================================== | |
| # (PARTE 4: L贸gica de Ejecuci贸n) -> An谩lisis Fiel y Reescritura Limpia | |
| # ========================================== | |
| 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, "<p style='color:#ef4444;'>Error: No hay texto para analizar.</p>", "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 = "<div class='document-viewer'>" | |
| puntajes_totales = [] | |
| for oracion in oraciones: | |
| if es_metadato_o_encabezado(oracion): | |
| html_visor += f"<span style='color: #94a3b8; font-style: italic;' title='Metadatos / Encabezado omitido'>{oracion} </span>" | |
| 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"<span class='{clase}' title='{tooltip}: {round(score,1)}%'>{oracion} </span>" | |
| html_visor += "</div>" | |
| 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 | |
| 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( | |
| """ | |
| # <span class='title-header'>CVision Advanced AI Detector & Humanizer</span> | |
| ### 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="<div class='document-viewer' style='color:#64748b; text-align:center;'><br><br>El documento analizado aparecer谩 aqu铆 con resaltado de IA.<br>Rojo: Alta IA | Amarillo: Dudoso | Verde: Humano</div>") | |
| with gr.Row(): | |
| indicador_ia = gr.Label(label="Probabilidad Global de IA") | |
| estado_sis = gr.Textbox(label="Registro del Sistema", interactive=False) | |
| gr.HTML("<hr style='border-color: #1e293b; margin: 20px 0;'>") | |
| 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() |