Commit c1b5546b authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

fix(lead): Ensure Strict AI Selection via product_ids, Prevent search...

fix(lead): Ensure Strict AI Selection via product_ids, Prevent search fallbacks and add proper Device ID logic
parent 4b7fa72d
...@@ -24,7 +24,7 @@ from langfuse import Langfuse, get_client as get_langfuse ...@@ -24,7 +24,7 @@ from langfuse import Langfuse, get_client as get_langfuse
from common.conversation_manager import get_conversation_manager from common.conversation_manager import get_conversation_manager
from common.langfuse_client import async_flush_langfuse, get_callback_handler from common.langfuse_client import async_flush_langfuse, get_callback_handler
from agent.controller_helpers import load_user_insight_from_redis from agent.controller_helpers import load_user_insight_from_redis, save_user_insight_to_redis
from agent.helper import handle_post_chat_async from agent.helper import handle_post_chat_async
from .graph import get_lead_stage_agent from .graph import get_lead_stage_agent
...@@ -196,9 +196,15 @@ async def lead_stage_chat_controller( ...@@ -196,9 +196,15 @@ async def lead_stage_chat_controller(
) )
# ═══ 5. SAVE CONVERSATION (background) ═══ # ═══ 5. SAVE CONVERSATION (background) ═══
# Chỉ lưu text của Stylist + compact product list (sku + name)
compact_products = [
{"sku": p.get("sku_code") or p.get("sku") or p.get("internal_ref_code", ""),
"name": p.get("name") or p.get("product_name", "")}
for p in products[:6]
]
response_payload = { response_payload = {
"ai_response": ai_response, "ai_response": ai_response, # Chỉ text của Stylist
"product_ids": products, "product_ids": compact_products, # Compact: SKU + tên (không full object)
"lead_stage": lead_stage, "lead_stage": lead_stage,
} }
...@@ -211,6 +217,27 @@ async def lead_stage_chat_controller( ...@@ -211,6 +217,27 @@ async def lead_stage_chat_controller(
conversation_id=conversation_id, conversation_id=conversation_id,
) )
# ── Persist Updated Insight về Redis ──
updated_insight = chat_result.get("updated_insight")
if updated_insight and identity_key:
import json as _json
insight_str = _json.dumps(updated_insight, ensure_ascii=False)
background_tasks.add_task(save_user_insight_to_redis, str(identity_key), insight_str)
logger.info(f"💾 [Lead Controller] Queued insight persist | stage={updated_insight.get('STAGE', '?')}")
# Lưu Postgres (persistent, không bị TTL như Redis)
from common.lead_flow_postgres import save_lead_turn
background_tasks.add_task(
save_lead_turn,
device_id=str(device_id or identity_key),
conv_id=str(conversation_id or session_id),
human_message=query,
ai_response_text=ai_response,
products=products,
lead_stage=lead_stage,
)
# ═══ 6. RETURN ═══ # ═══ 6. RETURN ═══
return { return {
"status": "success", "status": "success",
......
This diff is collapsed.
...@@ -32,7 +32,7 @@ PRODUCT_LINE_MAP: dict[str, list[str]] = { ...@@ -32,7 +32,7 @@ PRODUCT_LINE_MAP: dict[str, list[str]] = {
"Áo bra active": ["áo bra active", "áo bra", "bra", "áo tập", "áo thể thao"], "Áo bra active": ["áo bra active", "áo bra", "bra", "áo tập", "áo thể thao"],
"Áo Body": ["áo body", "áo croptop", "croptop", "baby tee", "áo lửng", "áo dáng ngắn", "áo ôm"], "Áo Body": ["áo body", "áo croptop", "croptop", "baby tee", "áo lửng", "áo dáng ngắn", "áo ôm"],
"Áo ba lỗ": ["áo ba lỗ", "áo sát nách", "tanktop", "tank top", "áo dây", "áo 2 dây", "áo hai dây"], "Áo ba lỗ": ["áo ba lỗ", "áo sát nách", "tanktop", "tank top", "áo dây", "áo 2 dây", "áo hai dây"],
"Váy liền": ["váy liền", "đầm", "váy công sở", "đầm công sở", "váy liền thân", "đầm suông"], "Váy liền": ["váy liền", "đầm", "váy công sở", "đầm công sở", "váy liền thân", "đầm suông", "váy dài"],
"Chân váy": ["chân váy", "váy maxi", "váy midi", "chân váy dài", "chân váy chữ a", "chân váy công sở", "váy ngắn"], "Chân váy": ["chân váy", "váy maxi", "váy midi", "chân váy dài", "chân váy chữ a", "chân váy công sở", "váy ngắn"],
"Quần giả váy": ["quần giả váy", "quần váy", "skort"], "Quần giả váy": ["quần giả váy", "quần váy", "skort"],
"Quần soóc": ["quần soóc", "quần đùi", "quần short", "quần lửng", "quần ngố", "short", "quần đùi nam", "quần đùi nữ"], "Quần soóc": ["quần soóc", "quần đùi", "quần short", "quần lửng", "quần ngố", "short", "quần đùi nam", "quần đùi nữ"],
...@@ -63,6 +63,9 @@ PRODUCT_LINE_MAP: dict[str, list[str]] = { ...@@ -63,6 +63,9 @@ PRODUCT_LINE_MAP: dict[str, list[str]] = {
"Bộ mặc nhà": ["bộ mặc nhà", "đồ ngủ", "đồ mặc nhà", "đồ ở nhà", "bộ lanh"], "Bộ mặc nhà": ["bộ mặc nhà", "đồ ngủ", "đồ mặc nhà", "đồ ở nhà", "bộ lanh"],
"Blazer": ["blazer", "áo vest", "vest"], "Blazer": ["blazer", "áo vest", "vest"],
"Tất": ["tất", "vớ", "bao chân", "vớ chân", "tất chân"], "Tất": ["tất", "vớ", "bao chân", "vớ chân", "tất chân"],
"Quần tất": ["quần tất", "quần vớ", "tất quần"],
"Mũ thể thao": ["mũ thể thao", "mũ snapback", "mũ lưỡi trai", "cap"],
"Khẩu trang": ["khẩu trang", "mask", "mặt nạ vải"],
"Túi xách": ["túi xách", "túi"], "Túi xách": ["túi xách", "túi"],
} }
......
This diff is collapsed.
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
Lead Flow — Lead Stage AI Experiment Lead Flow — Lead Stage AI Experiment
═══════════════════════════════════════ */ ═══════════════════════════════════════ */
body { margin:0; min-height:100vh; background:var(--background); font-family:var(--font-sans); color:var(--foreground); } html { height: 100%; margin: 0; padding: 0; }
body { margin:0; height:100%; overflow:hidden; display:flex; flex-direction:column; background:var(--background); font-family:var(--font-sans); color:var(--foreground); }
/* ═══ STAGE PROGRESS BAR ═══ */ /* ═══ STAGE PROGRESS BAR ═══ */
.stage-bar { .stage-bar {
background: var(--card); background: var(--card);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
padding: 12px 20px; padding: 12px 20px;
position: sticky;
top: 0;
z-index: 50; z-index: 50;
flex-shrink: 0;
} }
.stage-bar-inner { .stage-bar-inner {
...@@ -59,6 +59,24 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var ...@@ -59,6 +59,24 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
.history-select {
margin-left: 10px;
padding: 6px 12px;
border-radius: 6px;
border: 1px solid var(--border);
outline: none;
font-size: 13px;
background: var(--card);
color: var(--fg);
font-weight: 500;
cursor: pointer;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.history-select:hover {
border-color: #bbb;
}
.stage-label { .stage-label {
font-size: 10px; font-size: 10px;
font-weight: 600; font-weight: 600;
...@@ -107,12 +125,16 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var ...@@ -107,12 +125,16 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var
/* ═══ MAIN LAYOUT ═══ */ /* ═══ MAIN LAYOUT ═══ */
.app-container { .app-container {
flex: 1;
min-height: 0;
width: 100%;
display: grid; display: grid;
grid-template-columns: 1fr 340px; grid-template-columns: 1fr 340px;
grid-template-rows: 1fr;
gap: 0; gap: 0;
max-width: 1100px; max-width: 1100px;
margin: 0 auto; margin: 0 auto;
height: calc(100vh - 56px); overflow: hidden;
} }
/* ═══ CHAT PANEL ═══ */ /* ═══ CHAT PANEL ═══ */
...@@ -121,15 +143,19 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var ...@@ -121,15 +143,19 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var
flex-direction: column; flex-direction: column;
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
background: var(--card); background: var(--card);
min-height: 0;
overflow: hidden;
} }
.chat-messages { .chat-messages {
flex: 1; flex: 1;
min-height: 0;
overflow-y: auto; overflow-y: auto;
padding: 20px 24px; padding: 20px 24px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
scroll-behavior: smooth;
} }
.msg { .msg {
...@@ -256,6 +282,7 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var ...@@ -256,6 +282,7 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var
padding: 14px 20px; padding: 14px 20px;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
background: var(--card); background: var(--card);
flex-shrink: 0;
} }
.input-wrapper { .input-wrapper {
...@@ -307,6 +334,7 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var ...@@ -307,6 +334,7 @@ body { margin:0; min-height:100vh; background:var(--background); font-family:var
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
min-height: 0;
} }
.debug-title { .debug-title {
......
/* ═══════════════════════════════════════
Lead Flow — Product Size Picker Popup
═══════════════════════════════════════ */
/* ── Popup Overlay ── */
.lf-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,.55);
z-index: 9000;
opacity: 0;
pointer-events: none;
transition: opacity .25s ease;
backdrop-filter: blur(3px);
}
.lf-overlay.active {
opacity: 1;
pointer-events: auto;
}
/* ── Size Picker Popup ── */
.lf-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -44%) scale(.94);
z-index: 9001;
background: #1e2028;
border: 1px solid rgba(255,255,255,.08);
border-radius: 16px;
max-width: 440px;
width: 92vw;
max-height: 88vh;
overflow-y: auto;
box-shadow: 0 24px 64px rgba(0,0,0,.55);
opacity: 0;
pointer-events: none;
transition: all .28s cubic-bezier(.4,0,.2,1);
}
.lf-popup.active {
opacity: 1;
pointer-events: auto;
transform: translate(-50%, -50%) scale(1);
}
.lf-popup::-webkit-scrollbar { width: 4px; }
.lf-popup::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 4px; }
/* ── Popup Header ── */
.lf-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid rgba(255,255,255,.07);
}
.lf-popup-header h3 {
margin: 0;
font-size: 13px;
font-weight: 700;
letter-spacing: .6px;
color: #fff;
text-transform: uppercase;
}
.lf-popup-close {
width: 30px; height: 30px;
border: none;
background: rgba(255,255,255,.08);
color: #aaa;
font-size: 16px;
cursor: pointer;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
transition: all .2s;
}
.lf-popup-close:hover { background: rgba(255,255,255,.16); color: #fff; transform: rotate(90deg); }
/* ── Popup Body ── */
.lf-popup-body { padding: 20px; }
/* ── Product Info Row ── */
.lf-product-row {
display: flex;
gap: 14px;
margin-bottom: 20px;
background: rgba(0,0,0,.2);
padding: 12px;
border-radius: 12px;
}
.lf-product-img {
width: 76px; height: 96px;
object-fit: cover;
border-radius: 8px;
background: #fff;
flex-shrink: 0;
}
.lf-product-info { flex: 1; min-width: 0; }
.lf-product-name {
color: #f0f0f0;
font-size: 13.5px;
font-weight: 600;
line-height: 1.45;
margin-bottom: 6px;
}
.lf-product-sku {
color: #888;
font-size: 11px;
font-family: 'Consolas', monospace;
background: rgba(255,255,255,.05);
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
margin-bottom: 8px;
}
.lf-product-price {
display: flex;
align-items: baseline;
gap: 8px;
flex-wrap: wrap;
}
.lf-price-sale { color: #be1e2d; font-weight: 700; font-size: 15px; }
.lf-price-orig { color: #666; font-size: 12px; text-decoration: line-through; }
.lf-price-badge {
background: rgba(190,30,45,.15);
color: #ff6b6b;
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
}
/* ── Sections ── */
.lf-section { margin-bottom: 18px; }
.lf-section-label {
color: #aaa;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .5px;
margin-bottom: 10px;
}
/* ── Color Swatches ── */
.lf-colors { display: flex; gap: 8px; flex-wrap: wrap; }
.lf-color-dot {
width: 28px; height: 28px;
border-radius: 50%;
border: 3px solid transparent;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0,0,0,.4);
transition: all .2s;
}
.lf-color-dot.active { border-color: #be1e2d; transform: scale(1.12); }
/* ── Size Buttons ── */
.lf-sizes { display: flex; flex-wrap: wrap; gap: 8px; }
.lf-size-btn {
background: #2a2c35;
border: 1px solid #383a45;
border-radius: 10px;
padding: 9px 14px;
color: #ddd;
cursor: pointer;
font-weight: 600;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
min-width: 60px;
transition: all .2s;
font-family: inherit;
}
.lf-size-btn:hover:not(:disabled) { background: rgba(255,255,255,.1); border-color: #555; }
.lf-size-btn.selected { background: rgba(190,30,45,.15); border-color: #be1e2d; color: #ff7675; }
.lf-size-btn:disabled { opacity: .38; cursor: not-allowed; }
.lf-size-label { font-size: 13px; }
.lf-size-qty { font-size: 10px; font-weight: 500; }
.lf-size-qty.in-stock { color: #00d26a; }
.lf-size-qty.no-stock { color: #ff4757; }
.lf-size-stock-loading { color: #666; font-size: 12px; padding: 8px 0; }
/* ── Add to Cart Button ── */
.lf-add-btn {
width: 100%;
padding: 15px;
background: #444;
color: #888;
border: none;
border-radius: 12px;
font-weight: 800;
font-size: 13px;
letter-spacing: .5px;
cursor: pointer;
margin-top: 8px;
transition: all .2s;
font-family: inherit;
}
.lf-add-btn.ready {
background: linear-gradient(135deg, #be1e2d, #f03e3e);
color: #fff;
box-shadow: 0 6px 20px rgba(190,30,45,.35);
cursor: pointer;
}
.lf-add-btn.ready:hover { background: linear-gradient(135deg, #d32f2f, #fc5c65); transform: translateY(-1px); }
/* ── Detail link ── */
.lf-detail-link {
display: block;
text-align: center;
font-size: 12px;
color: #5b9cf6;
text-decoration: none;
margin-top: 12px;
transition: color .2s;
}
.lf-detail-link:hover { color: #93c5fd; text-decoration: underline; }
/* ── Loading spinner ── */
.lf-spinner {
display: inline-block;
width: 14px; height: 14px;
border: 2px solid rgba(255,255,255,.15);
border-top-color: #be1e2d;
border-radius: 50%;
animation: lf-spin 0.8s linear infinite;
vertical-align: middle;
margin-right: 6px;
}
@keyframes lf-spin { to { transform: rotate(360deg); } }
/* ── Product card clickable ── */
.product-card.clickable {
cursor: pointer;
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
}
.product-card.clickable:hover {
border-color: #be1e2d;
transform: translateY(-2px);
box-shadow: 0 4px 14px rgba(0,0,0,.12);
}
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lead Stage AI — Canifa AI</title>
<!-- Base styles -->
<link rel="stylesheet" href="/static/css/theme.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/lead-flow.css">
<link rel="stylesheet" href="/static/css/cart.css">
<!-- Lead-flow local styles (popup + card) -->
<link rel="stylesheet" href="/static/lead_flow/lead-flow.css">
<!-- Frame detection + Markdown -->
<script src="/static/js/frame-detect.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<!-- ═══ STAGE PROGRESS BAR ═══ -->
<div class="stage-bar">
<div class="stage-bar-inner">
<span class="logo">CANIFA AI</span>
<select id="historySelect" class="history-select" onchange="switchConversation(this.value)">
<option value="">-- Mở phiên Chat Mới --</option>
</select>
<div style="display: flex; gap: 5px; align-items: center; margin-left: auto; padding-right: 15px;">
<label style="font-size: 0.8em; color: rgba(255,255,255,0.7);">Device ID:</label>
<input type="text" id="deviceIdInput"
style="width: 130px; padding: 4px 8px; border-radius: 4px; border: 1px solid #444; background: #222; color: #fff; font-size: 0.85em;"
onchange="handleDeviceIdChange()">
</div>
<div class="stage-node stage-1" id="stageNode1">
<div class="stage-dot" id="stageDot1">1</div>
<span class="stage-label" id="stageLabel1">Awareness</span>
</div>
<div class="stage-connector conn-1" id="conn1"><div class="fill"></div></div>
<div class="stage-node stage-2" id="stageNode2">
<div class="stage-dot" id="stageDot2">2</div>
<span class="stage-label" id="stageLabel2">Interest</span>
</div>
<div class="stage-connector conn-2" id="conn2"><div class="fill"></div></div>
<div class="stage-node stage-3" id="stageNode3">
<div class="stage-dot" id="stageDot3">3</div>
<span class="stage-label" id="stageLabel3">Consideration</span>
</div>
<div class="stage-connector conn-3" id="conn3"><div class="fill"></div></div>
<div class="stage-node stage-4" id="stageNode4">
<div class="stage-dot" id="stageDot4">4</div>
<span class="stage-label" id="stageLabel4">Decision</span>
</div>
<div class="stage-connector conn-4" id="conn4"><div class="fill"></div></div>
<div class="stage-node stage-5" id="stageNode5">
<div class="stage-dot" id="stageDot5">5</div>
<span class="stage-label" id="stageLabel5">Retention</span>
</div>
</div>
</div>
<!-- ═══ APP LAYOUT ═══ -->
<div class="app-container">
<!-- CHAT PANEL -->
<div class="chat-panel">
<div class="chat-messages" id="chatMessages">
<div class="empty-state" id="emptyState">
<div class="icon">🛍️</div>
<h3>Lead Stage AI — Guided Shopping</h3>
<p>AI phân tích giai đoạn mua hàng của bạn và tự điều chỉnh phong cách tư vấn realtime.</p>
<div class="quick-prompts">
<button class="quick-prompt" onclick="sendQuick('Xin chào!')">👋 Xin chào!</button>
<button class="quick-prompt" onclick="sendQuick('Tìm áo polo nam')">👔 Tìm áo polo nam</button>
<button class="quick-prompt" onclick="sendQuick('So sánh 2 mẫu áo khoác giúp mình')">🔍 So sánh SP</button>
<button class="quick-prompt" onclick="sendQuick('Mình muốn mua luôn, chốt đơn!')">🛒 Chốt đơn</button>
<button class="quick-prompt" onclick="sendQuick('Mình mua hôm trước, muốn đổi size')">🔄 Đổi trả</button>
</div>
</div>
</div>
<div class="typing-indicator" id="typingIndicator">
<span></span><span></span><span></span>
</div>
<div class="chat-input-bar">
<div class="input-wrapper">
<input type="text" class="chat-input" id="chatInput"
placeholder="Nhập tin nhắn..." autocomplete="off"
onkeydown="if(event.key==='Enter') sendMessage()">
<button class="send-btn" id="sendBtn" onclick="sendMessage()"></button>
</div>
</div>
</div>
<!-- DEBUG PANEL -->
<div class="debug-panel">
<div class="debug-title">🧠 Lead Stage Debug Panel</div>
<div class="debug-card" id="pipelineCard">
<h4>⚡ Pipeline Trace</h4>
<div class="pipeline-trace" id="pipelineTrace">
<div style="font-size:11px;color:var(--muted-fg)">Gửi tin nhắn để xem pipeline.</div>
</div>
</div>
<div class="debug-card" id="timingCard">
<h4>⏱ Timing</h4>
<div class="timing-grid">
<div class="timing-cell">
<div class="t-value" id="tClassifier"></div>
<div class="t-label">Classifier</div>
</div>
<div class="timing-cell">
<div class="t-value" id="tStylist"></div>
<div class="t-label">Stylist</div>
</div>
<div class="timing-cell">
<div class="t-value" id="tTotal"></div>
<div class="t-label">Total</div>
</div>
</div>
</div>
<div class="debug-card" id="stageCard">
<h4>🎯 Current Stage</h4>
<div class="debug-row">
<span class="label">Stage</span>
<span class="value" id="dStage"></span>
</div>
<div class="debug-row">
<span class="label">Stage Name</span>
<span class="value" id="dStageName"></span>
</div>
<div class="debug-row">
<span class="label">Tone</span>
<span class="value" id="dTone"></span>
</div>
<div class="debug-row">
<span class="label">Confidence</span>
<span class="value" id="dConfidence"></span>
</div>
<div class="confidence-bar-outer">
<div class="confidence-bar-inner" id="confBar" style="width:0%"></div>
</div>
</div>
<div class="debug-card" id="reasoningCard">
<h4>💭 Classifier Reasoning</h4>
<div class="debug-reasoning" id="dReasoning">Chưa có dữ liệu.</div>
</div>
<div class="debug-card" id="hintsCard">
<h4>📋 Behavioral Hints</h4>
<div class="debug-hints" id="dHints">
<div class="debug-hint">Chưa có gợi ý.</div>
</div>
</div>
<div class="debug-card" id="historyCard">
<h4>📊 Stage History</h4>
<div class="stage-history" id="stageHistory">
<div style="font-size:11px;color:var(--muted-fg)">Chưa có lịch sử.</div>
</div>
</div>
</div>
</div>
<!-- ═══ PRODUCT POPUP ═══ -->
<div class="lf-overlay" id="lfOverlay" onclick="closeProductPopup()"></div>
<div class="lf-popup" id="lfPopup">
<div class="lf-popup-header">
<h3>THÔNG TIN SẢN PHẨM</h3>
<button class="lf-popup-close" onclick="closeProductPopup()"></button>
</div>
<div class="lf-popup-body" id="lfPopupBody">
<!-- rendered by lead-popup.js -->
</div>
</div>
<!-- ═══ SHOPPING CART FAB + DRAWER ═══ -->
<button class="cart-fab" id="cartFab" onclick="toggleCart()">
🛒<span class="cart-badge" id="cartBadge" style="display:none">0</span>
</button>
<div class="cart-overlay" id="cartOverlay" onclick="toggleCart()"></div>
<div class="cart-drawer" id="cartDrawer">
<div class="cart-drawer-header">
<span>GIỎ HÀNG CỦA BẠN</span>
<button onclick="toggleCart()"></button>
</div>
<div class="cart-drawer-items" id="cartItems"></div>
<div class="cart-drawer-footer">
<div class="cart-total">
<span>Tổng cộng:</span>
<span class="cart-total-price" id="cartTotal"></span>
</div>
<button class="cart-send-btn" id="cartSendBtn" onclick="sendCartToBot()" disabled>GỬI YÊU CẦU ĐẶT HÀNG</button>
<button class="cart-send-btn" id="cartClearBtn" onclick="clearCart()" style="background:#444;margin-top:10px;">XÓA GIỎ HÀNG</button>
</div>
</div>
<!-- ═══ SIZE MODAL (for cart.js fallback) ═══ -->
<div class="size-modal-overlay" id="sizeModalOverlay" onclick="closeSizeModal && closeSizeModal()"></div>
<div class="size-modal" id="sizeModal">
<div class="size-modal-header">
<span>CHỌN SIZE VÀ MÀU SẮC</span>
<button class="size-modal-close" onclick="closeSizeModal && closeSizeModal()"></button>
</div>
<div class="size-modal-body" id="sizeModalBody"></div>
</div>
<div class="cart-toast" id="cartToast"></div>
<!-- ═══ SCRIPTS — thứ tự quan trọng ═══ -->
<script src="/static/lead_flow/lead-popup.js"></script> <!-- popup tự standalone -->
<script src="/static/js/cart.js"></script> <!-- cart drawer + addToCart -->
<script src="/static/lead_flow/lead-flow.js"></script> <!-- chat logic -->
</body>
</html>
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment