CVision-assitant's picture
Update app.py
6cdbb41 verified
Raw History Blame Contribute Delete
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
# ==========================================
@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, "<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
@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(
"""
# <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()