- System prompt: SIEMPRE devuelve search_query en ingles - Diccionario de traducciones (balatas→Brake Pad, etc.) - Busca directo sin preguntar mas info - Fallback: extrae keywords si AI no da search_query Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
162 lines
5.5 KiB
Python
162 lines
5.5 KiB
Python
# /home/Autopartes/pos/blueprints/chat_bp.py
|
|
"""Chat blueprint: AI-powered parts lookup via natural language.
|
|
|
|
Endpoints (all under /pos/api/chat):
|
|
POST / — send a message, get AI response + catalog search results
|
|
"""
|
|
|
|
from flask import Blueprint, request, jsonify, g
|
|
from middleware import require_auth
|
|
from tenant_db import get_master_conn, get_tenant_conn
|
|
from services import catalog_service, ai_chat
|
|
|
|
chat_bp = Blueprint("chat", __name__, url_prefix="/pos/api/chat")
|
|
|
|
|
|
@chat_bp.route("", methods=["POST"])
|
|
@require_auth("catalog.view")
|
|
def chat():
|
|
body = request.get_json(force=True)
|
|
user_message = (body.get("message") or "").strip()
|
|
if not user_message:
|
|
return jsonify({"error": "message required"}), 400
|
|
|
|
history = body.get("history") or []
|
|
|
|
# Call AI
|
|
ai_response = ai_chat.chat(user_message, history)
|
|
|
|
search_results = []
|
|
vehicle_match = None
|
|
|
|
master = None
|
|
tenant = None
|
|
try:
|
|
# If AI suggests a search query, run it against the catalog
|
|
search_query = ai_response.get("search_query")
|
|
vehicle = ai_response.get("vehicle")
|
|
|
|
if search_query or vehicle:
|
|
master = get_master_conn()
|
|
tenant = get_tenant_conn(g.tenant_id)
|
|
branch_id = g.branch_id
|
|
|
|
# Try to resolve vehicle to MYE
|
|
if vehicle and master:
|
|
vehicle_match = _resolve_vehicle(master, vehicle)
|
|
|
|
# Run catalog search if we have a search query
|
|
# Also search if AI identified a vehicle but didn't give a search_query
|
|
effective_query = search_query
|
|
if not effective_query and vehicle:
|
|
# Extract likely part keywords from the user's message
|
|
import re
|
|
# Remove brand/model/year from message to get the part description
|
|
part_words = user_message.lower()
|
|
for remove in [vehicle.get('brand',''), vehicle.get('model',''), str(vehicle.get('year',''))]:
|
|
part_words = part_words.replace(remove.lower(), '')
|
|
part_words = re.sub(r'necesito|quiero|busco|para|un|una|el|la|de|del|los|las|mi|\d{4}', '', part_words).strip()
|
|
if len(part_words) >= 3:
|
|
effective_query = part_words
|
|
|
|
if effective_query and master and tenant:
|
|
try:
|
|
results = catalog_service.smart_search(
|
|
master, effective_query, tenant, branch_id, limit=10
|
|
)
|
|
search_results = results if results else []
|
|
except Exception:
|
|
pass # search failure is non-fatal
|
|
|
|
except Exception:
|
|
pass # DB failure is non-fatal for chat
|
|
finally:
|
|
if master:
|
|
try:
|
|
master.close()
|
|
except Exception:
|
|
pass
|
|
if tenant:
|
|
try:
|
|
tenant.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return jsonify(
|
|
{
|
|
"response": ai_response.get("message", ""),
|
|
"search_results": search_results,
|
|
"vehicle": vehicle_match or ai_response.get("vehicle"),
|
|
}
|
|
)
|
|
|
|
|
|
def _resolve_vehicle(master_conn, vehicle):
|
|
"""Try to resolve AI-extracted vehicle info to brand_id/model_id in DB."""
|
|
brand_name = (vehicle.get("brand") or "").upper().strip()
|
|
model_name = (vehicle.get("model") or "").strip()
|
|
year = vehicle.get("year")
|
|
|
|
if not brand_name:
|
|
return vehicle
|
|
|
|
cur = master_conn.cursor()
|
|
result = dict(vehicle)
|
|
|
|
try:
|
|
# Find brand
|
|
cur.execute(
|
|
"SELECT id_brand, name_brand FROM brands WHERE UPPER(name_brand) = %s",
|
|
(brand_name,),
|
|
)
|
|
brand_row = cur.fetchone()
|
|
if brand_row:
|
|
result["brand_id"] = brand_row[0]
|
|
result["brand"] = brand_row[1]
|
|
|
|
# Find model
|
|
if model_name:
|
|
cur.execute(
|
|
"""SELECT m.id_model, m.name_model
|
|
FROM models m
|
|
WHERE m.brand_id = %s
|
|
AND UPPER(m.name_model) LIKE %s
|
|
ORDER BY m.name_model
|
|
LIMIT 5""",
|
|
(brand_row[0], f"%{model_name.upper()}%"),
|
|
)
|
|
model_row = cur.fetchone()
|
|
if model_row:
|
|
result["model_id"] = model_row[0]
|
|
result["model"] = model_row[1]
|
|
|
|
# Find year -> MYE
|
|
if year:
|
|
cur.execute(
|
|
"""SELECT mye.id_mye, y.year_car, e.name_engine, mye.trim_level
|
|
FROM model_year_engine mye
|
|
JOIN years y ON y.id_year = mye.year_id
|
|
JOIN engines e ON e.id_engine = mye.engine_id
|
|
WHERE mye.model_id = %s AND y.year_car = %s
|
|
ORDER BY e.name_engine
|
|
LIMIT 10""",
|
|
(model_row[0], int(year)),
|
|
)
|
|
mye_rows = cur.fetchall()
|
|
if mye_rows:
|
|
result["mye_options"] = [
|
|
{
|
|
"mye_id": r[0],
|
|
"year": r[1],
|
|
"engine": r[2],
|
|
"trim": r[3],
|
|
}
|
|
for r in mye_rows
|
|
]
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
cur.close()
|
|
|
|
return result
|