feat(whatsapp): QWEN primary AI backend, Hermes fallback, conversation history, vehicle persistence, demo prompts

- Add QWEN (qwen3.6) as primary AI backend with short system prompt
- Hermes remains as fallback with 45s timeout
- Increase QWEN timeout to 35s, max_tokens to 4000
- Add conversation history loading from whatsapp_messages (last 4 msgs)
- Persist detected vehicle in whatsapp_sessions table
- Add 'limpiar chat' / 'nuevo chat' / 'reset' commands to clear history
- Fix CSS conflict: rename whatsapp chat-panel classes to wa-chat-panel
- Fix JS ID conflicts with chat.js widget (waChatPanel, waChatMessages, etc.)
- Improve no-stock response: conversational with alternatives
- Split search_query by | for multi-part lookups
- Add DEMO_PROMPTS.md and DEMO_PROMPTS_V2.md
This commit is contained in:
2026-05-06 20:27:14 +00:00
parent 371d72887e
commit ff45905b49
33 changed files with 3040 additions and 445 deletions

View File

@@ -109,11 +109,30 @@ def confirm_quotation(tenant_conn, phone):
return qid
# ─── In-memory last-shown-part per phone ────────────────────────────
# ─── Persistent last-shown-part per phone ────────────────────────────
# Tracks what part the bot last showed so "cotizar" knows what to add.
# Key: phone (clean, no @lid). Value: dict with inventory item info.
# Stored in tenant DB table whatsapp_sessions so it survives restarts.
_last_shown = {}
_WHATSAPP_SESSIONS_SQL = """
CREATE TABLE IF NOT EXISTS whatsapp_sessions (
phone VARCHAR(50) PRIMARY KEY,
last_shown JSONB,
vehicle JSONB,
updated_at TIMESTAMP DEFAULT NOW()
);
"""
def _ensure_sessions_table(tenant_conn):
cur = tenant_conn.cursor()
cur.execute(_WHATSAPP_SESSIONS_SQL)
# Migrate: add vehicle column if table already existed without it
cur.execute("""
ALTER TABLE whatsapp_sessions
ADD COLUMN IF NOT EXISTS vehicle JSONB
""")
tenant_conn.commit()
cur.close()
def set_last_shown_part(phone, part_info):
@@ -122,15 +141,110 @@ def set_last_shown_part(phone, part_info):
part_info: dict with keys inventory_id, part_number, name, brand,
price, stock, unit
"""
_last_shown[phone] = part_info
# In-memory fallback for when tenant_conn is not available
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
import json
cur.execute("""
INSERT INTO whatsapp_sessions (phone, last_shown, updated_at)
VALUES (%s, %s, NOW())
ON CONFLICT (phone) DO UPDATE SET last_shown = EXCLUDED.last_shown, updated_at = NOW()
""", (phone, json.dumps(part_info)))
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(f"[WA-SESSION] Failed to persist last_shown for {phone}: {e}")
def get_last_shown_part(phone):
return _last_shown.get(phone)
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
cur.execute("SELECT last_shown FROM whatsapp_sessions WHERE phone = %s", (phone,))
row = cur.fetchone()
cur.close()
conn.close()
if row and row[0]:
return row[0]
except Exception as e:
print(f"[WA-SESSION] Failed to read last_shown for {phone}: {e}")
return None
def clear_last_shown(phone):
_last_shown.pop(phone, None)
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
cur.execute("DELETE FROM whatsapp_sessions WHERE phone = %s", (phone,))
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(f"[WA-SESSION] Failed to clear last_shown for {phone}: {e}")
def set_vehicle(phone, vehicle):
"""Store the detected vehicle for this phone number.
vehicle: dict with keys brand, model, year
"""
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
import json
cur.execute("""
INSERT INTO whatsapp_sessions (phone, vehicle, updated_at)
VALUES (%s, %s, NOW())
ON CONFLICT (phone) DO UPDATE SET vehicle = EXCLUDED.vehicle, updated_at = NOW()
""", (phone, json.dumps(vehicle)))
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(f"[WA-SESSION] Failed to persist vehicle for {phone}: {e}")
def get_vehicle(phone):
"""Retrieve the stored vehicle for this phone number."""
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
cur.execute("SELECT vehicle FROM whatsapp_sessions WHERE phone = %s", (phone,))
row = cur.fetchone()
cur.close()
conn.close()
if row and row[0]:
return row[0]
except Exception as e:
print(f"[WA-SESSION] Failed to read vehicle for {phone}: {e}")
return None
def clear_session(phone):
"""Clear all session data (last_shown + vehicle) for this phone."""
from tenant_db import get_tenant_conn
try:
conn = get_tenant_conn(11)
_ensure_sessions_table(conn)
cur = conn.cursor()
cur.execute("DELETE FROM whatsapp_sessions WHERE phone = %s", (phone,))
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(f"[WA-SESSION] Failed to clear session for {phone}: {e}")
# ─── Quotation CRUD ─────────────────────────────────────────────────