call_flow/flow/views.py

892 lines
37 KiB

# views.py
from django.shortcuts import render
import os
from django.http import JsonResponse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
import json
from pathlib import Path
import shutil
import re
import subprocess
# Helper function to make safe filenames
def get_safe_filename(name):
return re.sub(r'[^a-zA-Z0-9_-]', '_', name).strip()
# Vista para renderizar la página principal
def create_tabs_flow(request):
return render(request, 'flow.html')
# Vista para listar todas las pestañas existentes
def list_tabs(request):
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
tabs = []
if os.path.exists(data_dir):
for filename in os.listdir(data_dir):
if filename.startswith('tab_') and filename.endswith('.json'):
# Extract tabId and tabName from filename
# Assuming filename format: tab_<tabId>_<safeTabName>.json
match = re.match(r'tab_(\d+)_(.+)\.json', filename)
if match:
tab_id = match.group(1)
# Optionally, extract tabName from filename or read from JSON
file_path = os.path.join(data_dir, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
tab_name = data.get('tabName', f'Pestaña {tab_id}')
tabs.append({
'name': tab_name,
'id': tab_id
})
return JsonResponse({'tabs': tabs})
# Función para crear y guardar archivos de texto desde el contenido del node3
@csrf_exempt
def save_tts(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
title = data.get('title')
content = data.get('content')
audio_file = data.get('audioFile') # Obtenemos el nombre del archivo de audio
if not audio_file:
audio_file = 'saludo_claude' # Valor por defecto si no se proporciona
file_name = f'{tab_name}.py'
py_filepath = os.path.join('/var/lib/asterisk/agi-bin/', file_name)
# Escapar llaves en el contenido
file_content = f"""#!/usr/bin/env python3
import speech_recognition as sr
import re
import tts
import subprocess
import os
from asterisk.agi import AGI
import anthropic
import json
agi = AGI()
class CallHandler:
def __init__(self):
self.audio_path = "/var/lib/asterisk/sounds/grabacion_usuario.wav"
self.tab_id = self.get_tab_id()
def get_tab_id(self):
# Obtener el tab_id desde una variable de entorno AGI
# Asegúrate de pasar esta variable desde el Dialplan
tab_id = agi.env.get('TAB_ID', None)
if not tab_id:
agi.verbose("TAB_ID no fue proporcionado. Utilizando 'default_tab_id'.", 1)
return 'default_tab_id'
return tab_id
def grabar_llamada(self):
callerid = agi.env.get('agi_callerid', '')
uniqueid = agi.env.get('agi_uniqueid', '')
# Registrar la información de la llamada
agi.verbose(f"CallerID: {{callerid}}", 1)
agi.verbose(f"UniqueID: {{uniqueid}}", 1)
# Iniciar la grabación
agi.verbose("Iniciando grabación...", 1)
try:
result = agi.record_file(
filename='/var/lib/asterisk/sounds/grabacion_usuario',
format='wav',
timeout=4000, # Puedes ajustar el tiempo de espera según tus necesidades
offset=0,
beep=True,
silence=0,
escape_digits='#'
)
except Exception as e:
agi.verbose(f"Error al grabar: {{e}}", 1)
else:
agi.verbose(f"Grabación completada. Resultado: {{result}}", 1)
self.detectar_audio()
def detectar_audio(self):
recognizer = sr.Recognizer()
with sr.AudioFile(self.audio_path) as source:
audio_data = recognizer.record(source)
try:
text = recognizer.recognize_google(audio_data, language="es-ES")
agi.verbose(f"La pregunta es: {{text}}", 1)
# Llamar a la función consultar_claude con el texto reconocido
respuesta = self.consultar_claude(text)
cleaned_text = re.sub(r'^(?:\\*+|\\d+)', '', respuesta).strip()
cleaned_text = cleaned_text.replace('%', '')
tts.convert_text_to_speech(cleaned_text)
ext = self.determine_extension(respuesta)
# Reproducir la respuesta generada por Claude
try:
tts.convert_text_to_speech(cleaned_text)
agi.stream_file('resp_claude')
except Exception as e:
agi.verbose(f"Error al convertir texto a voz: {{e}}", 1)
if ext is not None and ext != '':
try:
resultado_dial = agi.execute("EXEC Dial", f"Local/{{str(ext)}}@from-internal,20")
agi.verbose(f"Resultado del Dial: {{resultado_dial}}", 1)
except Exception as e:
agi.verbose(f"Error ejecutando Dial en AGI: {{e}}", 1)
else:
agi.verbose("La variable ext no tiene un valor válido.", 1)
agi.stream_file('algomas')
# Volver a grabar si es necesario
self.grabar_llamada()
except sr.RequestError as e:
agi.verbose(f"Error al conectarse con el servicio de reconocimiento de voz: {{e}}", 1)
except sr.UnknownValueError:
agi.verbose("No se pudo entender el audio", 1)
agi.stream_file('no_entendi')
self.grabar_llamada()
def determine_extension(self, input_str):
import unicodedata
def normalize_str(s):
return ''.join(
c for c in unicodedata.normalize('NFD', s)
if unicodedata.category(c) != 'Mn'
).lower()
agi.verbose(self.tab_id)
# Definir la ruta al archivo de condiciones basado en tab_id
conditions_file = f"/home/Flow_IA/IVR/tab_"""+ tab_id +"""_conditions.json"
first_word = input_str.split()[0] if input_str.split() else ''
try:
with open(conditions_file, 'r', encoding='utf-8') as f:
conditions_data = json.load(f)
# Iterar sobre todas las condiciones de node3
for node_cond in conditions_data.get('node3_conditions', []):
for cond in node_cond.get('conditions', []):
condition_str = cond.get('condition_string', '').strip()
extension_number = cond.get('extensionNumber', '').strip()
if first_word == normalize_str(condition_str):
agi.stream_file('resp_claude')
agi.verbose(f"Condición encontrada: {{condition_str}} -> Extensión: {{extension_number}}", 1)
try:
resultado_dial = agi.execute("EXEC Dial", f"Local/{str(extension_number)}@from-internal,20")
agi.verbose(f"Resultado del Dial: {resultado_dial}", 1)
except Exception as e:
agi.verbose(f"Error ejecutando Dial en AGI: {e}")
except Exception as e:
agi.verbose(f"Error al leer o procesar el archivo de condiciones: {{e}}", 1)
# Si no se encuentra ninguna condición que coincida
agi.verbose("No se encontró una condición que coincida con la entrada del usuario.", 1)
return None
def consultar_claude(self, text):
# Leer extensiones (este comando parece específico para tu configuración)
command = """ + '"""grep -E \'^\[|^callerid=\' /etc/asterisk/pjsip.endpoint.conf | awk \'{ \
if ($0 ~ /^\\[/) { \
gsub(/[\\[\\]]/, "", $1); \
ext = $1; \
} else if ($0 ~ /^callerid=/) { \
split($0, arr, "="); \
callerid = arr[2]; \
printf "Extension: " ext ", CallerID: " callerid "; "; \
} \
}\'"""' + """
result = subprocess.run(command, shell=True, capture_output=True, text=True)
output = result.stdout.strip()
# Crear el cliente Anthropic pasando la API key
client = anthropic.Anthropic(api_key='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')
message = client.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=200,
temperature=0,
system=(
""" + f"'''{content}'''" + """
),
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": text
}
]
}
]
)
respuesta_claude = "".join([msg.text for msg in message.content])
respuesta_claude = respuesta_claude.replace('%', '')
agi.verbose(f"Respuesta de Claude: {respuesta_claude}")
return respuesta_claude
if __name__ == "__main__":
agi.answer()
agi.stream_file('""" + audio_file + """')
call_handler = CallHandler()
call_handler.grabar_llamada()
"""
try:
with open(py_filepath, 'w', encoding='utf-8') as file:
file.write(file_content)
os.chmod(py_filepath, 0o755)
subprocess.run(['sudo', 'chown', 'asterisk:asterisk', py_filepath], check=True)
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'Error al crear el archivo: {e}'}, status=500)
return JsonResponse({
'status': 'success',
'filePath': py_filepath
})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
@csrf_exempt
def save_tab_data(request):
if request.method != 'POST':
return JsonResponse({'error': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName') # Nombre de la pestaña
nodes = data.get('nodes')
connections = data.get('connections')
if not tab_id or nodes is None or connections is None or not tab_name:
return JsonResponse({'error': 'Datos incompletos'}, status=400)
# Sanitize tab name
safe_tab_name = get_safe_filename(tab_name)
# Filename format: tab_<tabId>_<safe_tab_name>.json
filename = f'tab_{tab_id}_{safe_tab_name}.json'
file_path = os.path.join(settings.BASE_DIR, 'flow', 'data', filename)
# Filtrar los datos para cada tipo de nodo
filtered_nodes = []
for node in nodes:
node_type = node.get('nodeType')
if node_type == 'node1':
filtered_nodes.append({
'id': node.get('id'),
'title': node.get('title'),
'extensionNumber': node.get('extensionNumber'),
'position': node.get('position'),
'nodeType': node_type,
})
elif node_type == 'node2':
filtered_nodes.append({
'id': node.get('id'),
'title': node.get('title'),
'audioFile': node.get('audioFile', ""),
'position': node.get('position'),
'nodeType': node_type,
})
elif node_type == 'node3':
filtered_node = {
'id': node.get('id'),
'title': node.get('title'),
'content': node.get('content'),
'position': node.get('position'),
'nodeType': node_type,
'textFile': node.get('textFile', ""),
'conditions': node.get('conditions', []) # Añadido para condiciones dinámicas
}
filtered_nodes.append(filtered_node)
elif node_type == 'node4':
filtered_nodes.append({
'id': node.get('id'),
'title': node.get('title'),
'position': node.get('position'),
'nodeType': node_type,
'extensionNumber': node.get('extensionNumber', "") # Incluido extensionNumber
})
else:
# Otros tipos de nodos
filtered_nodes.append(node)
# Asegurarse de que data directory exists
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
os.makedirs(data_dir, exist_ok=True)
# Guardar el archivo .json con los datos filtrados
with open(file_path, 'w', encoding='utf-8') as f:
json.dump({'tabName': tab_name, 'nodes': filtered_nodes, 'connections': connections}, f, ensure_ascii=False, indent=4)
# **Nueva Sección: Extraer Condiciones de node3 y Crear Archivo .json en /home/Flow_IA/IVR/**
# Definir la ruta donde se crearán los archivos .json
ivr_dir = '/home/Flow_IA/IVR/'
# Asegurarse de que el directorio exista
os.makedirs(ivr_dir, exist_ok=True)
# Preparar la estructura de datos para las condiciones
conditions_data = {
'tabId': tab_id,
'tabName': tab_name,
'node3_conditions': []
}
# Crear un diccionario de nodos para acceso rápido
node_dict = {node['id']: node for node in filtered_nodes}
# Crear un diccionario de tipos de nodos
node_types = {node['id']: node['nodeType'] for node in filtered_nodes}
for node in filtered_nodes:
if node['nodeType'] == 'node3':
node_info = {
'nodeId': node['id'],
'title': node['title'],
'conditions': node.get('conditions', [])
}
# Inicializar una lista para almacenar los extensionNumbers de los node4 conectados
extension_numbers = []
# Buscar node4s conectados a este node3
for conn in connections:
if conn['startNodeId'] == node['id']:
end_node_id = conn['endNodeId']
if node_types.get(end_node_id) == 'node4':
node4 = node_dict[end_node_id]
extension_number = node4.get('extensionNumber', "")
extension_numbers.append(extension_number)
elif conn['endNodeId'] == node['id']:
start_node_id = conn['startNodeId']
if node_types.get(start_node_id) == 'node4':
node4 = node_dict[start_node_id]
extension_number = node4.get('extensionNumber', "")
extension_numbers.append(extension_number)
# Incluir los extensionNumbers en las condiciones del node3
# Suponiendo que cada condición corresponde a un node4 conectado
for idx, condition in enumerate(node_info['conditions']):
if idx < len(extension_numbers):
condition['extensionNumber'] = extension_numbers[idx]
else:
condition['extensionNumber'] = ""
conditions_data['node3_conditions'].append(node_info)
# Definir el nombre del archivo, por ejemplo: tab_<tab_id>_conditions.json
conditions_filename = f'tab_{tab_id}_conditions.json'
conditions_file_path = os.path.join(ivr_dir, conditions_filename)
# Guardar las condiciones en el archivo .json
with open(conditions_file_path, 'w', encoding='utf-8') as json_file:
json.dump(conditions_data, json_file, ensure_ascii=False, indent=4)
return JsonResponse({'message': 'Datos guardados exitosamente'})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
# Vista para renombrar una pestaña
@csrf_exempt
def rename_tab(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
new_tab_name = data.get('newTabName')
if not tab_id or not new_tab_name:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
safe_new_tab_name = get_safe_filename(new_tab_name)
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
# Find the existing file for the tab_id
existing_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not existing_files:
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
old_filename = existing_files[0]
old_file_path = os.path.join(data_dir, old_filename)
# New filename
new_filename = f'tab_{tab_id}_{safe_new_tab_name}.json'
new_file_path = os.path.join(data_dir, new_filename)
# Rename the file
os.rename(old_file_path, new_file_path)
# Update the tabName inside JSON
with open(new_file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
tab_data['tabName'] = new_tab_name
with open(new_file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({'status': 'success', 'message': 'Pestaña renombrada exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para cargar los datos de una pestaña específica
def load_tab_data(request, tab_id):
try:
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
# Find the JSON file for the tab_id
matching_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not matching_files:
return JsonResponse({'nodes': [], 'connections': [], 'tabName': f'Pestaña {tab_id}'})
file_path = os.path.join(data_dir, matching_files[0])
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Asegurarse de que node1, node3 y node4 no tengan audioFiles
for node in data.get('nodes', []):
node_type = node.get('nodeType')
if node_type in ['node1', 'node3', 'node4']:
node.pop('audioFile', None) # Corregido 'audioFiles' a 'audioFile'
# Para node4, no hay 'receivedString' sino 'extensionNumber', asegurarse de manejar correctamente
return JsonResponse(data)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
# Nueva Vista para eliminar archivos de Node3 cuando se actualiza
@csrf_exempt
def delete_files(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
old_title = data.get('oldTitle')
if not tab_id or not node_id or not node_type or not old_title:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node3
if node_type != "node3":
return JsonResponse({'status': 'error', 'message': 'Este nodo no tiene archivos para eliminar'}, status=200)
tab_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
text_dir = os.path.join(tab_dir, 'text_files')
os.makedirs(text_dir, exist_ok=True)
# Generar nombres de archivo basados en el antiguo título, node_type y node_id
safe_old_title = "".join([c if c.isalnum() else "_" for c in old_title])
filename_base = f"{safe_old_title}_{node_type}_{node_id}"
txt_filename = f"{filename_base}.txt"
txt_filepath = os.path.join(text_dir, txt_filename)
# Eliminar el archivo de texto si existe
if os.path.exists(txt_filepath):
os.remove(txt_filepath)
return JsonResponse({'status': 'success', 'message': 'Archivo de texto eliminado correctamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para subir audio para Node2
@csrf_exempt
def upload_audio(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
tab_id = request.POST.get('tabId')
node_id = request.POST.get('nodeId') # Agregamos nodeId
node_type = request.POST.get('nodeType')
title = request.POST.get('title')
audio_file = request.FILES.get('audioFile')
if not tab_id or not node_id or not node_type or not title or not audio_file:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node2
if node_type != "node2":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node2'}, status=400)
# Directorios específicos para la pestaña
tab_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
audio_dir = os.path.join(tab_dir, 'audio_files')
os.makedirs(audio_dir, exist_ok=True)
# Generar nombre único para el archivo de audio usando node_id
safe_title = "".join([c if c.isalnum() else "_" for c in title])
filename_base = f"{safe_title}_{node_type}_{node_id}"
wav_filename = f"{filename_base}.wav"
wav_filepath = os.path.join(audio_dir, wav_filename)
# Guardar el archivo de audio en /media/tabs
with open(wav_filepath, 'wb+') as destination:
for chunk in audio_file.chunks():
destination.write(chunk)
# Copiar el archivo a /var/spool/asterisk/recordings
asterisk_dir = '/var/lib/asterisk/sounds'
os.makedirs(asterisk_dir, exist_ok=True) # Asegurarse de que el directorio exista
asterisk_filepath = os.path.join(asterisk_dir, wav_filename)
shutil.copy(wav_filepath, asterisk_filepath)
# Generar URL del archivo
audio_file_url = os.path.join(settings.MEDIA_URL, 'tabs', f'tab_{tab_id}', 'audio_files', wav_filename)
return JsonResponse({
'status': 'success',
'audioFile': audio_file_url,
'asteriskFile': asterisk_filepath # Confirmación de la ruta en Asterisk
})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar audio de Node2
@csrf_exempt
def delete_audio(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
old_audio_file = data.get('oldAudioFile')
if not tab_id or not node_id or not node_type or not old_audio_file:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node2
if node_type != "node2":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node2'}, status=400)
# Obtener la ruta completa del archivo de audio
audio_file_path = os.path.join(settings.BASE_DIR, old_audio_file.replace(settings.MEDIA_URL, ''))
if os.path.exists(audio_file_path):
os.remove(audio_file_path)
return JsonResponse({'status': 'success', 'message': 'Archivo de audio eliminado correctamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para renombrar audio de Node2
@csrf_exempt
def rename_audio(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
old_title = data.get('oldTitle')
new_title = data.get('newTitle')
current_audio_file = data.get('currentAudioFile')
if not tab_id or not node_id or not node_type or not old_title or not new_title or not current_audio_file:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node2
if node_type != "node2":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node2'}, status=400)
# Directorios específicos para la pestaña
tab_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
audio_dir = os.path.join(tab_dir, 'audio_files')
os.makedirs(audio_dir, exist_ok=True)
# Generar nombres de archivo basados en los títulos antiguos y nuevos y node_id
safe_old_title = "".join([c if c.isalnum() else "_" for c in old_title])
safe_new_title = "".join([c if c.isalnum() else "_" for c in new_title])
old_filename_base = f"{safe_old_title}_{node_type}_{node_id}"
new_filename_base = f"{safe_new_title}_{node_type}_{node_id}"
old_wav_filename = f"{old_filename_base}.wav"
new_wav_filename = f"{new_filename_base}.wav"
old_wav_filepath = os.path.join(audio_dir, old_wav_filename)
new_wav_filepath = os.path.join(audio_dir, new_wav_filename)
# Renombrar el archivo de audio
if os.path.exists(old_wav_filepath):
os.rename(old_wav_filepath, new_wav_filepath)
# Generar URL del nuevo archivo
new_audio_file_url = os.path.join(settings.MEDIA_URL, 'tabs', f'tab_{tab_id}', 'audio_files', new_wav_filename)
return JsonResponse({
'status': 'success',
'newAudioFile': new_audio_file_url
})
else:
return JsonResponse({'status': 'error', 'message': 'Archivo de audio original no encontrado'}, status=404)
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para actualizar Node1 (título y extensión)
@csrf_exempt
def update_node1(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
node_id = data.get('nodeId') # Agregamos nodeId
node_type = data.get('nodeType')
new_title = data.get('newTitle')
new_extension = data.get('newExtension')
old_title = data.get('oldTitle')
directory = '/etc/asterisk/extensions_custom.conf'
file_content = f"""
exten => {new_extension},1,Answer()
exten => {new_extension},n,AGI(/var/lib/asterisk/agi-bin/{tab_name}.py)
exten => {new_extension},n,Hangup()
"""
try:
subprocess.run(['sudo', 'test', '-f', directory], check=True)
# Escribir en el archivo con permisos de root
subprocess.run(['sudo', 'bash', '-c', f'echo "{file_content}" >> {directory}'], check=True)
except subprocess.CalledProcessError as e:
print('No fue posible crear o escribir en el archivo:', e)
if not tab_id or not node_id or not node_type or not new_title or not new_extension or not old_title:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Procesar solo si el nodo es node1
if node_type != "node1":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node1'}, status=400)
# Directorios específicos para la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}.json')
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Buscar el nodo correspondiente y actualizar sus datos
for node in tab_data.get('nodes', []):
if node['id'] == node_id and node['nodeType'] == 'node1':
node['title'] = new_title
node['extensionNumber'] = new_extension
break
# Guardar el archivo .json actualizado
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({
'status': 'success',
'message': 'Nodo1 actualizado exitosamente'
})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar un nodo
@csrf_exempt
def delete_node(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
node_id = data.get('nodeId')
node_type = data.get('nodeType')
if not tab_id or not node_id or not node_type:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Cargar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
# Buscar el archivo JSON correspondiente a la pestaña
matching_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not matching_files:
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
file_path = os.path.join(data_dir, matching_files[0])
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Obtener los datos del nodo antes de eliminarlo
node_data = next((n for n in tab_data.get('nodes', []) if n['id'] == node_id), None)
if not node_data:
return JsonResponse({'status': 'error', 'message': 'Nodo no encontrado en la pestaña'}, status=404)
node_title = node_data.get('title', '')
# Eliminar el nodo y sus conexiones
tab_data['nodes'] = [node for node in tab_data.get('nodes', []) if node['id'] != node_id]
tab_data['connections'] = [conn for conn in tab_data.get('connections', [])
if conn['startNodeId'] != node_id and conn['endNodeId'] != node_id]
# Si es un nodo3 o nodo2, manejar eliminación de archivos
if node_type == 'node3':
# Lógica para eliminar archivos de texto
text_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'text_files')
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
txt_filename = f"{safe_title}_{node_type}_{node_id}.txt"
txt_filepath = os.path.join(text_dir, txt_filename)
if os.path.exists(txt_filepath):
os.remove(txt_filepath)
elif node_type == 'node2':
# Lógica para eliminar archivos de audio
audio_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}', 'audio_files')
safe_title = "".join([c if c.isalnum() else "_" for c in node_title])
wav_filename = f"{safe_title}_{node_type}_{node_id}.wav"
wav_filepath = os.path.join(audio_dir, wav_filename)
if os.path.exists(wav_filepath):
os.remove(wav_filepath)
# Guardar los datos actualizados
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({'status': 'success', 'message': 'Nodo y sus conexiones eliminados exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para eliminar una pestaña
@csrf_exempt
def delete_tab(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
if not tab_id or not tab_name:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
# Eliminar el archivo de datos de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
# Buscar el archivo JSON correspondiente a la pestaña
matching_files = [f for f in os.listdir(data_dir) if f.startswith(f'tab_{tab_id}_') and f.endswith('.json')]
if not matching_files:
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
file_path = os.path.join(data_dir, matching_files[0])
os.remove(file_path)
# Eliminar los archivos de medios asociados (audio_files y text_files)
media_dir = os.path.join(settings.MEDIA_ROOT, 'tabs', f'tab_{tab_id}')
if os.path.exists(media_dir):
shutil.rmtree(media_dir)
# Eliminar el archivo de condiciones JSON
ivr_dir = '/home/Flow_IA/IVR/'
conditions_filename = f'tab_{tab_id}_conditions.json'
conditions_file_path = os.path.join(ivr_dir, conditions_filename)
if os.path.exists(conditions_file_path):
os.remove(conditions_file_path)
return JsonResponse({'status': 'success', 'message': 'Pestaña eliminada exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
# Vista para actualizar Node4
@csrf_exempt
def update_node4(request):
if request.method != 'POST':
return JsonResponse({'status': 'error', 'message': 'Solo se permiten solicitudes POST'}, status=405)
try:
data = json.loads(request.body)
tab_id = data.get('tabId')
tab_name = data.get('tabName')
node_id = data.get('nodeId')
node_type = data.get('nodeType')
extension_number = data.get('extensionNumber') # Renombrado de receivedString a extensionNumber
if not tab_id or not node_id or not node_type or extension_number is None:
return JsonResponse({'status': 'error', 'message': 'Datos incompletos'}, status=400)
if node_type != "node4":
return JsonResponse({'status': 'error', 'message': 'Este endpoint solo es para node4'}, status=400)
# Guardar el número de extensión en el archivo de la pestaña
data_dir = os.path.join(settings.BASE_DIR, 'flow', 'data')
file_path = os.path.join(data_dir, f'tab_{tab_id}_{tab_name}.json')
if not os.path.exists(file_path):
return JsonResponse({'status': 'error', 'message': 'Archivo de pestaña no encontrado'}, status=404)
with open(file_path, 'r', encoding='utf-8') as f:
tab_data = json.load(f)
# Buscar el node4 correspondiente y actualizar su extensionNumber
node_found = False
for node in tab_data.get('nodes', []):
if node['id'] == node_id and node['nodeType'] == 'node4':
node['extensionNumber'] = extension_number
node_found = True
break
if not node_found:
return JsonResponse({'status': 'error', 'message': 'node4 no encontrado'}, status=404)
# Guardar el archivo .json actualizado
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(tab_data, f, ensure_ascii=False, indent=4)
return JsonResponse({'status': 'success', 'message': 'node4 actualizado exitosamente'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)