mauroleguiok commited on
Commit
a0e54de
·
verified ·
1 Parent(s): b30a9c5

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +572 -0
  2. requirements.txt +17 -0
app.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Copia de Modelo_Chatbot_Final_3_con_Gradio.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1sFAltehLtdNpHoQVsiDeikgSJkIDTWrD
8
+ """
9
+
10
+
11
+
12
+ import warnings
13
+ warnings.filterwarnings('ignore')
14
+
15
+ import json
16
+ import numpy as np
17
+ import pandas as pd
18
+ import random
19
+ from matplotlib import pyplot as plt
20
+ import seaborn as sns
21
+ from wordcloud import WordCloud,STOPWORDS
22
+ import missingno as msno
23
+
24
+ from sklearn.feature_extraction.text import CountVectorizer
25
+ from sklearn.model_selection import train_test_split
26
+ from sklearn.metrics import accuracy_score, precision_recall_fscore_support
27
+
28
+ #from keras.preprocessing import text
29
+ import keras
30
+ from keras.models import Sequential
31
+ from keras.layers import Dense,Embedding,LSTM,Dropout
32
+ from keras.callbacks import ReduceLROnPlateau
33
+
34
+ from tensorflow.keras.preprocessing.sequence import pad_sequences
35
+ import nltk
36
+ from nltk import word_tokenize
37
+ from nltk.stem import PorterStemmer
38
+
39
+ import torch
40
+ from torch.utils.data import Dataset
41
+
42
+ from transformers import AutoTokenizer, TFAutoModelForSequenceClassification
43
+ from transformers import pipeline
44
+ from transformers import DistilBertTokenizerFast
45
+ from transformers import BertForSequenceClassification, BertTokenizerFast
46
+ from transformers import TFDistilBertForSequenceClassification, Trainer, TFTrainingArguments
47
+ from transformers import BertTokenizer, TFBertForSequenceClassification, BertConfig
48
+ from transformers import TrainingArguments, Trainer, EarlyStoppingCallback
49
+
50
+ import re
51
+
52
+ import language_tool_python
53
+ import logging
54
+ import spacy
55
+
56
+ def load_json_file(filename):
57
+ with open(filename) as f:
58
+ file = json.load(f)
59
+ return file
60
+
61
+ filename = '/content/intents_aumentado.json'
62
+
63
+ intents = load_json_file(filename)
64
+
65
+ def create_df():
66
+ df = pd.DataFrame({
67
+ 'Pattern' : [],
68
+ 'Tag' : []
69
+ })
70
+
71
+ return df
72
+
73
+ df = create_df()
74
+ df
75
+
76
+ def extract_json_info(json_file, df):
77
+
78
+ for intent in json_file['intents']:
79
+
80
+ for pattern in intent['patterns']:
81
+
82
+ sentence_tag = [pattern, intent['tag']]
83
+ df.loc[len(df.index)] = sentence_tag
84
+
85
+ return df
86
+
87
+ df = extract_json_info(intents, df)
88
+ df.head()
89
+
90
+ df2 = df.copy()
91
+ df2.head()
92
+
93
+ import nltk
94
+ nltk.download('punkt_tab')
95
+
96
+ stemmer = PorterStemmer()
97
+ ignore_words=['?', '!', ',', '.']
98
+
99
+ def preprocess_pattern(pattern):
100
+ words = word_tokenize(pattern.lower())
101
+ stemmed_words = [stemmer.stem(word) for word in words if word not in ignore_words]
102
+ return " ".join(stemmed_words)
103
+
104
+ df['Pattern'] = df['Pattern'].apply(preprocess_pattern)
105
+
106
+ df2.head()
107
+
108
+ labels = df2['Tag'].unique().tolist()
109
+ labels = [s.strip() for s in labels]
110
+ labels
111
+
112
+ num_labels = len(labels)
113
+ id2label = {id:label for id, label in enumerate(labels)}
114
+ label2id = {label:id for id, label in enumerate(labels)}
115
+
116
+ id2label
117
+
118
+ label2id
119
+
120
+ df2['labels'] = df2['Tag'].map(lambda x: label2id[x.strip()])
121
+ df2.head()
122
+
123
+ X = list(df2['Pattern'])
124
+ X[:5]
125
+
126
+ y = list(df2['labels'])
127
+ y[:5]
128
+
129
+ X_train,X_test,y_train,y_test = train_test_split(X,y,random_state = 123)
130
+
131
+ model_name = "dccuchile/bert-base-spanish-wwm-cased"
132
+ max_len = 256
133
+
134
+ tokenizer = BertTokenizer.from_pretrained(model_name,
135
+ max_length=max_len)
136
+
137
+ model = BertForSequenceClassification.from_pretrained(model_name,
138
+ num_labels=num_labels,
139
+ id2label=id2label,
140
+ label2id = label2id)
141
+
142
+ train_encoding = tokenizer(X_train, truncation=True, padding=True)
143
+ test_encoding = tokenizer(X_test, truncation=True, padding=True)
144
+
145
+ full_data = tokenizer(X, truncation=True, padding=True)
146
+
147
+ class DataLoader(Dataset):
148
+
149
+ def __init__(self, encodings, labels):
150
+
151
+ self.encodings = encodings
152
+ self.labels = labels
153
+
154
+ def __getitem__(self, idx):
155
+
156
+ item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
157
+ item['labels'] = torch.tensor(self.labels[idx])
158
+ return item
159
+
160
+ def __len__(self):
161
+
162
+ return len(self.labels)
163
+
164
+ train_dataloader = DataLoader(train_encoding, y_train)
165
+ test_dataloader = DataLoader(test_encoding, y_test)
166
+
167
+ fullDataLoader = DataLoader(full_data, y_test)
168
+
169
+ def compute_metrics(pred):
170
+
171
+ labels = pred.label_ids
172
+ preds = pred.predictions.argmax(-1)
173
+ precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='macro')
174
+ acc = accuracy_score(labels, preds)
175
+
176
+ return {
177
+ 'accuracy': acc,
178
+ 'f1': f1,
179
+ 'precision': precision,
180
+ 'recall': recall
181
+ }
182
+
183
+ # Parametros finales del modelo
184
+ training_args = TrainingArguments(
185
+ output_dir='./output', # Carpeta donde se guardarán los modelos entrenados y checkpoints
186
+ do_train=True,
187
+ do_eval=True,
188
+ num_train_epochs=30, # Número total de épocas (pasadas completas por el dataset de entrenamiento)
189
+ per_device_train_batch_size=8, # Tamaño del batch de entrenamiento por dispositivo (GPU o CPU)
190
+ per_device_eval_batch_size=32, # Tamaño del batch de evaluación por dispositivo (mayor para evaluar más rápido)
191
+ gradient_accumulation_steps=4, # Acumula gradientes por 4 pasos antes de hacer una actualización (simula batch más grande)
192
+ learning_rate=2e-5, # Tasa de aprendizaje inicial
193
+ warmup_ratio=0.1, # Porcentaje de pasos de calentamiento (warmup) sobre el total de pasos de entrenamiento
194
+ weight_decay=0.1, # Regularización para evitar overfitting penalizando grandes pesos
195
+ lr_scheduler_type="cosine", # Tipo de scheduler para modificar la tasa de aprendizaje (coseno en este caso)
196
+ fp16=True, # Usa precisión mixta (float16) para acelerar entrenamiento si hay soporte (ej. en GPUs)
197
+ evaluation_strategy="steps",
198
+ eval_steps=50, # Evalúa el modelo cada 50 pasos de entrenamiento
199
+ save_strategy="steps",
200
+ save_steps=50, # Guarda el modelo cada 50 pasos
201
+ save_total_limit=3, # Mantiene solo los últimos 3 checkpoints, borra los anteriores
202
+ logging_strategy="steps",
203
+ logging_dir='./multi-class-logs', # Carpeta donde se guardarán los logs de entrenamiento cada 50 pasos
204
+ logging_steps=50, #
205
+ load_best_model_at_end=True, # Carga automáticamente el mejor modelo evaluado al finalizar el entrenamiento
206
+ metric_for_best_model="f1", # Métrica que se usa para definir cuál fue el "mejor" modelo
207
+ greater_is_better=True
208
+ )
209
+
210
+ trainer = Trainer(
211
+ model=model,
212
+ args=training_args,
213
+ train_dataset=train_dataloader,
214
+ eval_dataset=test_dataloader,
215
+ compute_metrics=compute_metrics,
216
+ callbacks=[EarlyStoppingCallback(early_stopping_patience=3)] # Frena el entrenamiento si no mejora la métrica de evaluación después de 3 evaluaciones consecutiva
217
+ )
218
+
219
+ import os
220
+
221
+ os.environ["WANDB_API_KEY"] = "4fb9dff0336a34ab812e86f91b6c3a877cb25b36"
222
+
223
+
224
+ trainer.train()
225
+
226
+ q=[trainer.evaluate(eval_dataset=df2) for df2 in [train_dataloader, test_dataloader]]
227
+
228
+ pd.DataFrame(q, index=["train","test"]).iloc[:,:5]
229
+
230
+ def predict(text):
231
+
232
+ inputs = tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors="pt").to("cuda")
233
+ outputs = model(**inputs)
234
+
235
+ probs = outputs[0].softmax(1)
236
+ pred_label_idx = probs.argmax()
237
+ pred_label = model.config.id2label[pred_label_idx.item()]
238
+
239
+ return probs, pred_label_idx, pred_label
240
+
241
+ text = "Hola"
242
+ predict(text)
243
+
244
+ model_path = "chatbot"
245
+ trainer.save_model(model_path)
246
+ tokenizer.save_pretrained(model_path)
247
+
248
+ !kaggle kernels output eyadgk/build-a-chatbot-with-bert-eda-vis -p /path/to/dest
249
+
250
+ model_path = "/content/chatbot"
251
+
252
+
253
+ model = BertForSequenceClassification.from_pretrained(model_path)
254
+ tokenizer= BertTokenizerFast.from_pretrained(model_path)
255
+ chatbot= pipeline("text-classification", model=model, tokenizer=tokenizer)
256
+
257
+ chatbot("Hola")
258
+
259
+ negaciones = [
260
+ "no", "nunca", "nadie", "ningún", "ninguna", "nada",
261
+ "jamás", "jamas", "ni", "tampoco", "de ninguna manera",
262
+ "en absoluto", "en ningún caso", "no es cierto",
263
+ "no estoy de acuerdo", "no me parece", "no creo",
264
+ "no quiero", "no puedo", "no quiero hacerlo", "no acepto", "no gracias"
265
+ ]
266
+
267
+ afirmaciones = [
268
+ "sí", "si", "claro", "por supuesto", "entendido", "estoy de acuerdo",
269
+ "acepto", "exacto", "correcto", "eso es", "está bien", "esta bien",
270
+ "claro que sí", "lo creo", "es cierto", "sin duda", "así es", "claro que si"
271
+ "perfecto", "me parece bien", "seguro", "definitivamente", "por supuesto"
272
+ ]
273
+
274
+ # Asumiendo que intents ya está definido
275
+ def obtener_respuesta_aleatoria(tag):
276
+ """Busca el intent correspondiente al tag y devuelve una respuesta aleatoria."""
277
+ for intent in intents['intents']:
278
+ if intent['tag'] == tag:
279
+ return random.choice(intent['responses'])
280
+ return "No tengo respuesta para eso."
281
+
282
+ # Asumiendo que intents ya está definido
283
+ def obtener_lista_de_respuesta(tag):
284
+ """Busca el intent correspondiente al tag y devuelve una respuesta aleatoria."""
285
+ for intent in intents['intents']:
286
+ if intent['tag'] == tag:
287
+ return intent['responses']
288
+ return "No tengo respuesta para eso."
289
+
290
+ historial_respuestas = {}
291
+
292
+ def obtener_respuesta_sin_repetir(label):
293
+ global historial_respuestas
294
+
295
+ # Si es la primera vez que se usa el label, inicializar historial
296
+ if label not in historial_respuestas:
297
+ historial_respuestas[label] = []
298
+
299
+ respuestas_posibles = obtener_lista_de_respuesta(label) # Lista de respuestas disponibles para el tag
300
+
301
+ # Filtrar respuestas que aún no se usaron
302
+ respuestas_disponibles = [r for r in respuestas_posibles if r not in historial_respuestas[label]]
303
+
304
+ if not respuestas_disponibles:
305
+ historial_respuestas[label] = [] # Resetear historial cuando se agoten todas
306
+
307
+ # Elegir una nueva respuesta sin repetir
308
+ nueva_respuesta = random.choice([r for r in respuestas_posibles if r not in historial_respuestas[label]])
309
+
310
+ # Agregarla al historial
311
+ historial_respuestas[label].append(nueva_respuesta)
312
+
313
+ return nueva_respuesta
314
+
315
+ def corregir_preguntas(texto, idioma='es'):
316
+ """
317
+ Corrige la ortografía y gramática del texto usando LanguageTool.
318
+ También ajusta signos de interrogación y acentos en palabras interrogativas.
319
+ Ignora correcciones sobre las palabras: 'unaj', 'arturo', 'jauretche'.
320
+ """
321
+ try:
322
+ # Agregar "?" si el texto tiene 3 o más palabras y no termina con "?"
323
+ if len(texto.split()) >= 3 and not texto.endswith("?"):
324
+ texto += "?"
325
+
326
+ # Palabras a ignorar (en minúsculas)
327
+ palabras_ignorar = ["unaj", "arturo", "jauretche", "profode"]
328
+ reemplazos = {}
329
+
330
+ # Reemplazar palabras ignoradas por marcadores temporales
331
+ def reemplazar_ignoradas(match):
332
+ palabra = match.group(0)
333
+ marcador = f"__IGNORAR_{len(reemplazos)}__"
334
+ reemplazos[marcador] = palabra
335
+ return marcador
336
+
337
+ patron_ignorar = re.compile(r'\b(' + '|'.join(palabras_ignorar) + r')\b', re.IGNORECASE)
338
+ texto_temporal = patron_ignorar.sub(reemplazar_ignoradas, texto)
339
+
340
+ # Inicializar el corrector de LanguageTool
341
+ tool = language_tool_python.LanguageToolPublicAPI(idioma)
342
+
343
+ # Obtener las correcciones sugeridas
344
+ texto_corregido = tool.correct(texto_temporal)
345
+
346
+ # Restaurar las palabras ignoradas
347
+ for marcador, palabra_original in reemplazos.items():
348
+ texto_corregido = texto_corregido.replace(marcador, palabra_original)
349
+
350
+ # Diccionario con palabras interrogativas y sus versiones acentuadas
351
+ palabras_interrogativas = {
352
+ "como": "cómo", "cuando": "cuándo", "donde": "dónde", "que": "qué",
353
+ "quien": "quién", "cual": "cuál", "cuanto": "cuánto"
354
+ }
355
+
356
+ # Si la oración es interrogativa, corregir solo la primera palabra interrogativa
357
+ if texto_corregido.endswith("?"):
358
+ palabras = texto_corregido[:-1].split() # Remover "?" y dividir en palabras
359
+ primera_encontrada = False
360
+
361
+ for i, palabra in enumerate(palabras):
362
+ palabra_limpia = palabra.lower().strip("¿") # Remover el signo "¿" si existe
363
+
364
+ # Si es una palabra interrogativa y es la primera encontrada, corregir
365
+ if palabra_limpia in palabras_interrogativas:
366
+ if not primera_encontrada:
367
+ palabras[i] = palabras_interrogativas[palabra_limpia]
368
+ primera_encontrada = True
369
+
370
+ texto_corregido = " ".join(palabras) + "?"
371
+
372
+ # Asegurar que la oración comienza con "¿"
373
+ if not texto_corregido.startswith("¿"):
374
+ texto_corregido = "¿" + texto_corregido
375
+
376
+ # Mantener solo el último signo "¿" y eliminar los anteriores
377
+ if texto_corregido.count("¿") > 1:
378
+ ultima_pos = texto_corregido.rfind("¿")
379
+ texto_corregido = texto_corregido[:ultima_pos].replace("¿", "") + texto_corregido[ultima_pos:]
380
+
381
+ return texto_corregido
382
+
383
+ except Exception:
384
+ return texto
385
+
386
+ def normalizar_clave(texto):
387
+ reemplazos = {
388
+ "á": "a", "é": "e", "í": "i", "ó": "o", "ú": "u",
389
+ "ä": "a", "ë": "e", "ï": "i", "ö": "o", "ü": "u"
390
+ }
391
+ for acentuada, normal in reemplazos.items():
392
+ texto = texto.replace(acentuada, normal)
393
+
394
+ return texto.strip().replace(" ", "+")
395
+
396
+ estado_chatbot = {
397
+ "esperando_confirmacion": False,
398
+ "opciones": [],
399
+ "texto_original": ""
400
+ }
401
+
402
+ def obtener_respuesta_chatbot(text):
403
+ global estado_chatbot
404
+
405
+ # Si no está esperando confirmación, resetea su estado antes de procesar la nueva consulta
406
+ if not estado_chatbot["esperando_confirmacion"]:
407
+ estado_chatbot["opciones"] = [] # Limpia opciones anteriores
408
+ estado_chatbot["texto_original"] = "" # Resetea el estado previo
409
+
410
+ if estado_chatbot["esperando_confirmacion"]:
411
+ if text.lower() in afirmaciones:
412
+ estado_chatbot["esperando_confirmacion"] = False # Se resetea el estado
413
+ respuesta = obtener_respuesta_sin_repetir(estado_chatbot["opciones"][0]["label"])
414
+ estado_chatbot["opciones"] = [] # Limpia opciones anteriores
415
+ estado_chatbot["texto_original"] = "" # Resetea el estado previo
416
+ return respuesta
417
+ elif text.lower() in negaciones:
418
+ if len(estado_chatbot["opciones"]) > 1:
419
+ estado_chatbot["esperando_confirmacion"] = False # Se resetea el estado
420
+ return obtener_respuesta_sin_repetir(estado_chatbot["opciones"][1]["label"])
421
+ else:
422
+ estado_chatbot["esperando_confirmacion"] = False # Se resetea el estado
423
+ return "No tengo más opciones, ¿podés reformular la pregunta?"
424
+
425
+ else:
426
+ return "Por favor, respondé 'sí' o 'no'."
427
+
428
+ prediction = chatbot(text)
429
+ prediction = sorted(prediction, key=lambda x: x["score"], reverse=True) # Ordenar por score
430
+
431
+ label_principal = prediction[0]["label"]
432
+ score_principal = prediction[0]["score"]
433
+
434
+ if score_principal >= 0.1:
435
+ print("(Grado de seguridad en la respuesta: ", score_principal, ")")
436
+ return obtener_respuesta_sin_repetir(label_principal)
437
+
438
+ elif 0.20 <= score_principal < 0.4:
439
+ estado_chatbot["esperando_confirmacion"] = True
440
+ estado_chatbot["opciones"] = prediction[:2]
441
+ estado_chatbot["texto_original"] = text
442
+
443
+ opciones_texto = ", ".join(
444
+ [f"{alt['label']} ({alt['score']:.2f})" for alt in estado_chatbot["opciones"]]
445
+ )
446
+ return f"No estoy seguro de la respuesta correscta. ¿Te referís a alguna de estas opciones? Opción 1: {opciones_texto} (Si/No)"
447
+
448
+ else:
449
+ print(f"No tengo una respuesta precisa. ¿Puedes decirme una palabra clave de tu pregunta para que pueda ayudarte? Ingresa una o dos palabras:")
450
+ clave = "info"
451
+ clave = normalizar_clave(clave)
452
+
453
+ # Verificar si la palabra clave no es una negación
454
+ if clave not in negaciones:
455
+ # Si no es una negación, proporcionar el enlace
456
+ return f"Prueba consultando el siguiente enlace: https://www.unaj.edu.ar/?s={clave} o reformula tu pregunta. Escribela a continuación:"
457
+ else:
458
+ # Si la clave es una negación, podrías manejarlo aquí
459
+ return "Comprendo, intenta reformular tu pregunta por favor para que pueda entenderla. Prueba usando frases cortas y claras."
460
+
461
+ faq = ['¿Cuándo puedo inscribirme a carreras?', '¿Qué carreras tiene la UNAJ?', '¿Qué posgrados tiene la UNAJ?', '¿Qué cursos de oficios tiene la UNAJ y cómo puedo inscribirme?', '¿Qué otras propuestas de formación ofrece la UNAJ?', '¿Puedo estudiar idiomas en la UNAJ?', '¿Qué hago si no puedo ingresar al SIU GUARANÍ?', '¿Cuándo comienza y termina el cuatrimestre?', '¿Dónde encuentro el calendario académico?', '¿Cuándo puedo reincorporarme?', '¿Cuándo puedo cambiar de carrera?', '¿Cómo pido equivalencias?', '¿Cómo pido una licencia estudiantil?']
462
+
463
+ for f in faq:
464
+ print(f)
465
+ print(obtener_respuesta_chatbot(corregir_preguntas(f.lower())))
466
+ print("-------------------------------------------------------------------------------------------------------------------------------------")
467
+
468
+ # Instalar librerías necesarias
469
+
470
+ import gradio as gr
471
+ from pyngrok import ngrok
472
+
473
+
474
+ import pandas as pd
475
+ from datetime import datetime
476
+ from bs4 import BeautifulSoup
477
+
478
+ # Nombre del archivo CSV
479
+ feedback_file = "feedback.csv"
480
+
481
+ # Inicializar CSV si no existe
482
+ def init_csv():
483
+ try:
484
+ pd.read_csv(feedback_file)
485
+ except FileNotFoundError:
486
+ df = pd.DataFrame(columns=["timestamp", "question", "response", "feedback"])
487
+ df.to_csv(feedback_file, index=False)
488
+
489
+ # Extrae el texto de la respuesta HTML
490
+ def limpiar_html(texto_html):
491
+ return BeautifulSoup(texto_html, "html.parser").get_text()
492
+
493
+ # Guardar el feedback cuando se hace clic en "Nuevo Mensaje"
494
+ def guardar_feedback(question, response, feedback):
495
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
496
+ response_limpia = limpiar_html(response)
497
+ new_data = pd.DataFrame([[timestamp, question, response_limpia, feedback]],
498
+ columns=["timestamp", "question", "response", "feedback"])
499
+ new_data.to_csv(feedback_file, mode='a', header=False, index=False)
500
+ return "", "", 10, gr.update(interactive=False) # limpia todo y desactiva "Nuevo Mensaje"
501
+
502
+ # Convierte las URL en hipervinculos clickeables
503
+ def convertir_urls_a_links(texto):
504
+ # Expresión regular para encontrar URLs
505
+ url_pattern = r"(https?://[^\s]+)"
506
+ # Reemplaza cada URL por una etiqueta <a>
507
+ return re.sub(url_pattern, r'<a href="\1" target="_blank">\1</a>', texto)
508
+
509
+ # Simulación de respuesta del chatbot (reemplazar por tu modelo real)
510
+ def chatbot_response(question):
511
+ mensaje_corregido = corregir_preguntas(question)
512
+ respuesta_raw = obtener_respuesta_chatbot(mensaje_corregido)
513
+ respuesta = convertir_urls_a_links(respuesta_raw)
514
+
515
+ # Agrega un contenedor con estilo para simular una caja
516
+ respuesta_contenedor = f"""
517
+ <div style='background-color:#2b2b2b; color:#f1f1f1; border:0px solid #515057;
518
+ padding:10px; border-radius:5px; white-space:pre-wrap'>
519
+ {respuesta}
520
+ </div>
521
+ """
522
+
523
+ return respuesta_contenedor, gr.update(visible=True, interactive=True)
524
+
525
+ # Inicializamos el CSV
526
+ init_csv()
527
+
528
+ # Interfaz Gradio
529
+ with gr.Blocks(css="""
530
+ body {
531
+ background-color: black;
532
+ color: #00ffff;
533
+ }
534
+ .gr-button {
535
+ background-color: #00ffff !important;
536
+ color: black !important;
537
+ }
538
+ .gr-textbox textarea, .gr-number input {
539
+ background-color: #111;
540
+ color: #00ffff;
541
+ border: 1px solid #00ffff;
542
+ }
543
+ """) as demo:
544
+ with gr.Row():
545
+ gr.HTML("<div style='flex:1'></div><img src='https://guarani.unaj.edu.ar/_comp/unaj/img/logo-transparente.png' height='60px' style='margin:10px'/>")
546
+
547
+ gr.Markdown("# Chatbot UNAJ\n## Hola! Soy Arturito, el bot de la UNAJ y estoy para responder tus preguntas sobre la Universidad Nacional Arturo Jauretche")
548
+
549
+ question_input = gr.Textbox(label="Mensaje", placeholder="Escribí tu consulta...")
550
+ submit_btn = gr.Button("Enviar Mensaje")
551
+
552
+ # Se usa un HTML para que los links de la respuesta sean clickeables
553
+ response_output = gr.HTML()
554
+
555
+ # Se coloca un slider que permite captar el feedback de la respuesta
556
+ feedback_slider = gr.Slider(minimum=1, maximum=10, value=10, step=1,
557
+ label="¿Qué tan útil fue la respuesta? (1 = Nada útil, 10 = Muy útil)", interactive=True)
558
+
559
+ # Aparece un boton para "Nuevo Mensaje" que limpia el cuadro de "Mensaje" y guarda la respuesta y puntuación.
560
+ new_message_btn = gr.Button("Nuevo Mensaje", visible=False)
561
+
562
+ # Evento al hacer clic en "Enviar Mensaje"
563
+ submit_btn.click(fn=chatbot_response,
564
+ inputs=question_input,
565
+ outputs=[response_output, new_message_btn])
566
+
567
+ # Evento al hacer clic en "Nuevo Mensaje"
568
+ new_message_btn.click(fn=guardar_feedback,
569
+ inputs=[question_input, response_output, feedback_slider],
570
+ outputs=[question_input, response_output, feedback_slider, new_message_btn])
571
+
572
+ demo.launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ transformers==4.17
2
+ torch
3
+ scikit-learn
4
+ numpy
5
+ pandas
6
+ matplotlib
7
+ seaborn
8
+ wordcloud
9
+ missingno
10
+ keras
11
+ tensorflow
12
+ nltk
13
+ language-tool-python
14
+ spacy
15
+ gradio
16
+ pyngrok
17
+ beautifulsoup4