Skip to content
Open

Fix #39

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def create_app():
load_configurations(app)
configure_logging()

print("ACCESS_TOKEN desde config:", app.config["ACCESS_TOKEN"])

# Import and register blueprints, if any
app.register_blueprint(webhook_blueprint)

Expand Down
1 change: 1 addition & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

def load_configurations(app):
load_dotenv()
# app.config["ACCESS_TOKEN"] = 'EACZD'
app.config["ACCESS_TOKEN"] = os.getenv("ACCESS_TOKEN")
app.config["YOUR_PHONE_NUMBER"] = os.getenv("YOUR_PHONE_NUMBER")
app.config["APP_ID"] = os.getenv("APP_ID")
Expand Down
197 changes: 184 additions & 13 deletions app/services/openai_service.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
from openai import OpenAI
import shelve
from dotenv import load_dotenv
import os
import time
import logging
import requests

load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
Expand All @@ -14,7 +16,7 @@
def upload_file(path):
# Upload a file with an "assistants" purpose
file = client.files.create(
file=open("../../data/airbnb-faq.pdf", "rb"), purpose="assistants"
file=open("../../data/faq.pdf", "rb"), purpose="assistants"
)


Expand All @@ -23,10 +25,10 @@ def create_assistant(file):
You currently cannot set the temperature for Assistant via the API.
"""
assistant = client.beta.assistants.create(
name="WhatsApp AirBnb Assistant",
instructions="You're a helpful WhatsApp assistant that can assist guests that are staying in our Paris AirBnb. Use your knowledge base to best respond to customer queries. If you don't know the answer, say simply that you cannot help with question and advice to contact the host directly. Be friendly and funny.",
name="WhatsApp Murrah Assistant",
instructions="Eres un asistente útil de WhatsApp que puede ayudar a los clientes que quieren hacer un pedido en nuestro restaurante Murrah. Usa tu base de conocimientos para responder de la mejor manera posible a las preguntas de los clientes. Si no sabes la respuesta, simplemente di que no puedes ayudar con esa pregunta y aconseja contactar al anfitrión directamente. Sé amigable y divertido.",
tools=[{"type": "retrieval"}],
model="gpt-4-1106-preview",
model="gpt-4o-mini",
file_ids=[file.id],
)
return assistant
Expand All @@ -44,28 +46,97 @@ def store_thread(wa_id, thread_id):


def run_assistant(thread, name):
# Retrieve the Assistant
assistant = client.beta.assistants.retrieve(OPENAI_ASSISTANT_ID)

# Run the assistant
# Verificar si el menú ya ha sido enviado
if verificar_menu_enviado(thread.id):
menu_enviado = True
else:
menu_enviado = False

# Definir la estructura del JSON que debe ser devuelta cuando el cliente termine
estructura_json = """
{
"cliente": "nombre_del_cliente",
"pedido": [
{"producto": "nombre_del_producto", "cantidad": cantidad},
{"producto": "nombre_del_producto", "cantidad": cantidad}
],
"hora_pedido": "2025-10-01T12:00:00Z (aqui va la hora del pedido)",
}
"""

# Ejecutar el asistente
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
# instructions=f"You are having a conversation with {name}",
instructions=f"Estás teniendo una conversación con {name}. Primero espera a que se muestre el menú, y luego espera las instrucciones del cliente sobre su pedido. \
Y cuando el cliente diga explicitamente 'CONFIRMAR', pero tambien recuerdale que tiene que decir 'CONFIRMAR' para acabar el pedido, \
finaliza el pedido y manda el siguiente json y solo el json:\n {estructura_json}\
Ahora espera la respuesta del cliente.",
)

# Wait for completion
# https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps#:~:text=under%20failed_at.-,Polling%20for%20updates,-In%20order%20to

# Esperar la finalización
while run.status != "completed":
# Be nice to the API
time.sleep(0.5)
run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)

# Retrieve the Messages
# Obtener los mensajes
messages = client.beta.threads.messages.list(thread_id=thread.id)
new_message = messages.data[0].content[0].text.value
logging.info(f"Generated message: {new_message}")
return new_message
logging.info(f"Mensaje generado: {new_message}")
response = new_message

# Si es la primera interacción y el menú aún no ha sido enviado
if not menu_enviado:
# Cargar el menú desde el archivo o base de datos
menu = cargar_menu_desde_txt() # Cargar el menú del archivo de texto
response = f"¡Hola {name}! ¡Bienvenido a Murrah! \nAquí está el menú: \n{menu}"

# Enviar el menú al cliente como el primer mensaje
message = client.beta.threads.messages.create(
thread_id=thread.id,
role="assistant",
content=response,
)

# Marcar que el menú ha sido enviado
marcar_menu_enviado(thread.id)

# Si el cliente menciona que ha terminado el pedido
elif "json" in new_message.lower():
# Persistir la confirmación del cliente
marcar_confirmacion(thread.id, True)

import re

try:
# Buscar el bloque entre ```json y ```
match = re.search(r"```json\s*(\{.*?\})\s*```", new_message, re.DOTALL)
if match:
json_str = match.group(1)
comanda_extraida = json.loads(json_str)
estado_pedido[thread.id] = comanda_extraida["pedido"]
logging.info(f"✅ Pedido guardado para {thread.id}: {estado_pedido[thread.id]}")
else:
raise ValueError("No se encontró bloque JSON válido en el mensaje")
except Exception as e:
logging.error(f"❌ Error al extraer comanda JSON: {e}")
return "Hubo un error al procesar tu pedido. Por favor intenta nuevamente."

# Finalizar el pedido y generar el JSON
comanda_json = finalizar_pedido(thread.id, name)
logging.info(f"Comanda finalizada: {json.dumps(comanda_json, indent=4)}")
estado_pedido[thread.id] = comanda_json
response = f"Gracias por tu pedido, {name}. ¡Lo estamos procesando!"

# Si el cliente no ha confirmado, pero la confirmación persiste (por si el servicio se cae)
elif verificar_confirmacion(thread.id):
response = "Tu pedido ya fue confirmado. Estamos procesando tu pedido."

# Devolver la respuesta generada
return response


def generate_response(message_body, wa_id, name):
Expand Down Expand Up @@ -95,3 +166,103 @@ def generate_response(message_body, wa_id, name):
new_message = run_assistant(thread, name)

return new_message

# Diccionario que almacena el pedido del cliente
estado_pedido = {}

def finalizar_pedido(wa_id, name):
"""
Función para finalizar el pedido del cliente y generar el JSON.
"""
if wa_id not in estado_pedido:
logging.warning(f"No hay pedido para {wa_id}")
return None

# Generamos el JSON de la comanda
comanda_json = {
"cliente": name,
"pedido": estado_pedido[wa_id]
}

try:
response = requests.post("http://localhost:5001/comandas", json=comanda_json, timeout=5)
response.raise_for_status()
logging.info("✅ Comanda enviada correctamente.")
except requests.exceptions.RequestException as e:
logging.error(f"❌ Error al enviar comanda: {e}")
return None

logging.info(f"📦 Comanda generada:\n{json.dumps(comanda_json, indent=4)}")
return comanda_json


def agregar_producto_pedido(wa_id, producto, cantidad, precio):
"""
Función para agregar un producto al pedido del cliente.
Si el producto ya está en el pedido, actualiza la cantidad.
"""
if wa_id not in estado_pedido:
estado_pedido[wa_id] = []

# Buscar si el producto ya está en el pedido
producto_existente = next((item for item in estado_pedido[wa_id] if item['producto'] == producto), None)

if producto_existente:
# Si el producto ya existe, actualizar la cantidad
producto_existente['cantidad'] += cantidad
else:
# Si el producto no existe, agregarlo
estado_pedido[wa_id].append({
'producto': producto,
'cantidad': cantidad,
'precio': precio
})

logging.info(f"Pedido actualizado para {wa_id}: {estado_pedido[wa_id]}")

def cargar_menu_desde_txt():
"""
Lee el contenido de un archivo .txt y lo retorna como un string.

Parámetros:
ruta_archivo (str): Ruta del archivo de texto.

Retorna:
str: Contenido del archivo.
"""
try:
with open("app/utils/menu.txt", 'r', encoding='utf-8') as archivo:
contenido = archivo.read()
return contenido
except FileNotFoundError:
return "Error: El archivo no se encontró."
except Exception as e:
return f"Error al leer el archivo: {str(e)}"

def marcar_menu_enviado(thread_id):
"""
Guarda en un archivo la información de que el menú ya ha sido enviado para este hilo.
"""
with shelve.open('estado_conversacion.db', writeback=True) as db:
db[thread_id] = True

def verificar_menu_enviado(thread_id):
"""
Verifica si el menú ya fue enviado para este hilo de conversación.
"""
with shelve.open('estado_conversacion.db') as db:
return db.get(thread_id, False)

def marcar_confirmacion(wa_id, confirmado):
"""
Guarda en un archivo la información de si el cliente ha confirmado o no su pedido.
"""
with shelve.open('estado_confirmacion.db', writeback=True) as db:
db[wa_id] = confirmado

def verificar_confirmacion(wa_id):
"""
Verifica si el cliente ha confirmado su pedido.
"""
with shelve.open('estado_confirmacion.db') as db:
return db.get(wa_id, False)
43 changes: 43 additions & 0 deletions app/services/straico_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import requests
import json

url = "https://api.straico.com/v1/prompt/completion"


headers = {
'Authorization': 'Bearer TY-AX7LCpGWb1qEh7DSGOV1ucPX3EhV1YwNoANr2IY89j3J6B5C',
'Content-Type': 'application/json'
}


def generate_response(message_body, wa_id, name):
payload = json.dumps({
"models": [
#"anthropic/claude-3.7-sonnet:thinking",
"meta-llama/llama-4-maverick"
],
"message": "Contexto",




#Cargar archivo para contexto
"file_urls": [
"url a reemplazar"
]
})
response = requests.request("POST", url, headers=headers, data=payload)

return response.text


print(generate_response("Hola, que hora es", None, "Felipe"))


def generaurl():
files=[
('file',('galaxy.jpg', open('C:/Users/GATOTEC18/Downloads/galaxy.jpg','rb'), 'image/jpeg'))
]
headers = {
# 'Content-Type': 'multipart/form-data' # Esta línea se comenta o elimina
}
9 changes: 9 additions & 0 deletions app/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask import Flask

def create_app():
app = Flask(__name__)

from .views import webhook_blueprint # 👈 muévelo aquí
app.register_blueprint(webhook_blueprint, url_prefix="/webhook")

return app
71 changes: 71 additions & 0 deletions app/utils/app_mostrar_comandas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from flask import Flask, request, jsonify, render_template_string
from datetime import datetime

app = Flask(__name__)
comandas = []

html_template = """
<!DOCTYPE html>
<html>
<head>
<title>🧾 Tablero de Comandas - Cocina</title>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5">
<style>
body { font-family: sans-serif; background: #1e1e2f; color: white; margin: 0; padding: 0; }
h1 { text-align: center; background: #333; padding: 20px 0; margin: 0; font-size: 2em; }
.grid { display: flex; flex-wrap: wrap; padding: 20px; justify-content: center; gap: 20px; }
.card {
background: #2c2c3c;
border-radius: 12px;
padding: 20px;
width: 300px;
box-shadow: 0 0 10px rgba(0,0,0,0.4);
transition: transform 0.3s;
}
.card:hover { transform: scale(1.03); }
.cliente { font-size: 1.2em; margin-bottom: 10px; }
.hora { font-size: 0.9em; color: #aaa; margin-bottom: 10px; }
ul { padding-left: 20px; }
li { margin-bottom: 5px; }
</style>
</head>
<body>
<h1>🧾 COMANDAS EN COCINA</h1>
<div class="grid">
{% for comanda in comandas %}
<div class="card">
<div class="cliente"><strong>👤 {{ comanda.cliente }}</strong></div>
<div class="hora">🕒 {{ comanda.hora_pedido }}</div>
<div><strong>📝 Pedido:</strong></div>
<ul>
{% for item in comanda.pedido %}
<li>{{ item.cantidad }} x {{ item.producto }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</body>
</html>
"""

@app.route("/", methods=["GET"])
def ver_comandas():
return render_template_string(html_template, comandas=comandas)

@app.route("/comandas", methods=["POST"])
def recibir_comanda():
data = request.get_json()
if not data:
return jsonify({"error": "No data provided"}), 400

# Agregar hora si no viene incluida
if "hora_pedido" not in data:
data["hora_pedido"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

comandas.append(data)
return jsonify({"status": "comanda recibida"}), 200

if __name__ == "__main__":
app.run(port=5001)
Loading