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

Upload copia_de_modelo_chatbot_final_3_con_gradio.py

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