diff --git a/demo/web/server.py b/demo/web/server.py
index d2ae96d..6e9dd87 100644
--- a/demo/web/server.py
+++ b/demo/web/server.py
@@ -712,15 +712,21 @@ def list_conversations(
try:
with conn.cursor() as cur:
sql = """
- SELECT cu.id, cu.customer_name, s.name AS salesperson_name,
+ SELECT DISTINCT cu.id, cu.customer_name, s.name AS salesperson_name,
cu.industry, cu.intent_level, cu.stage,
cu.summary, cu.key_points, cu.objections, cu.next_action,
cu.key_needs, cu.last_analysis,
(SELECT count(*) FROM message m
JOIN conversation conv ON conv.id = m.conversation_id
- WHERE conv.contact_id = cu.contact_id) AS msg_count
+ WHERE conv.contact_id = cu.contact_id) AS msg_count,
+ string_agg(DISTINCT p.category, ', ') AS deal_categories,
+ string_agg(m2.normalized_content, ' ') AS all_messages
FROM customer cu
JOIN salesperson s ON s.id = cu.salesperson_id
+ LEFT JOIN deal d ON d.customer_id = cu.id
+ LEFT JOIN product p ON p.id = d.product_id
+ LEFT JOIN conversation conv2 ON conv2.contact_id = cu.contact_id
+ LEFT JOIN message m2 ON m2.conversation_id = conv2.id
"""
conditions = []
params = []
@@ -732,11 +738,32 @@ def list_conversations(
params.append(stage)
if conditions:
sql += " WHERE " + " AND ".join(conditions)
- sql += " ORDER BY cu.salesperson_id, cu.id"
+ sql += """
+ GROUP BY cu.id, cu.customer_name, s.name,
+ cu.industry, cu.intent_level, cu.stage,
+ cu.summary, cu.key_points, cu.objections, cu.next_action,
+ cu.key_needs, cu.last_analysis
+ ORDER BY cu.salesperson_id, cu.id
+ """
cur.execute(sql, params)
- return [
- {
+ diet_keywords = ["减肥", "减脂", "瘦身", "B420", "纤益", "代餐", "体脂", "产后减肥", "节食"]
+ child_keywords = ["湿疹", "过敏", "鼻炎", "腹泻", "便秘", "免疫力", "胀气",
+ "拉肚子", "打喷嚏", "流鼻涕", "感冒", "食物过敏", "牛奶蛋白",
+ "宝宝", "宝妈", "儿童", "丹麦", "鼠李糖", "LGG", "合生元"]
+ results = []
+ for r in cur.fetchall():
+ deal_cats = r[13]
+ all_msgs = r[14] or ""
+ if deal_cats:
+ product_categories = deal_cats
+ elif any(kw in all_msgs for kw in diet_keywords):
+ product_categories = "女性减肥益生菌"
+ elif any(kw in all_msgs for kw in child_keywords):
+ product_categories = "儿童益生菌"
+ else:
+ product_categories = None
+ results.append({
"id": r[0], "customer_name": r[1],
"salesperson_name": r[2], "industry": r[3],
"intent_level": r[4], "stage": r[5],
@@ -747,9 +774,9 @@ def list_conversations(
"key_needs": parse_pg_array(r[10]),
"last_analysis": r[11].isoformat() if r[11] else None,
"msg_count": r[12],
- }
- for r in cur.fetchall()
- ]
+ "product_categories": product_categories,
+ })
+ return results
finally:
put_conn(conn)
diff --git a/demo/web/static/index.html b/demo/web/static/index.html
index ca6269c..9956d3c 100644
--- a/demo/web/static/index.html
+++ b/demo/web/static/index.html
@@ -230,14 +230,15 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
选择客户和产品,基于产品详情和真实对话生成标准范式对话模板
-
+
@@ -976,29 +977,121 @@ function renderAnalysisList() {
`).join('');
}
+let paradigmCustomers = [];
+let paradigmProducts = [];
+
function populateParadigmSelect() {
const sel = document.getElementById('paradigm-customer-select');
if (!sel) return;
- const currentVal = sel.value;
+ paradigmCustomers = conversationsData;
+ // 填充客户下拉
sel.innerHTML = '';
- conversationsData.forEach(c => {
+ paradigmCustomers.forEach(c => {
const opt = document.createElement('option');
opt.value = c.id;
opt.textContent = `${c.customer_name} (${c.salesperson_name}, ${c.stage})`;
sel.appendChild(opt);
});
- if (currentVal) sel.value = currentVal;
- // 填充产品选择
+ // 填充产品下拉
const productSel = document.getElementById('paradigm-product-select');
- if (productSel && productSel.options.length <= 1) {
- api('/products?status=active').then(products => {
- products.forEach(p => {
- const opt = document.createElement('option');
- opt.value = p.id;
- opt.textContent = `${p.name} ${p.spec || ''}`;
- productSel.appendChild(opt);
- });
- }).catch(() => {});
+ productSel.innerHTML = '';
+ api('/products?status=active').then(products => {
+ paradigmProducts = products;
+ products.forEach(p => {
+ const opt = document.createElement('option');
+ opt.value = p.id;
+ opt.textContent = `${p.name} ${p.spec || ''}`;
+ productSel.appendChild(opt);
+ });
+ }).catch(() => {});
+}
+
+/** 选择客户时,自动筛选匹配的产品 */
+function onParadigmCustomerChange() {
+ const customerId = document.getElementById('paradigm-customer-select').value;
+ const productSel = document.getElementById('paradigm-product-select');
+ const hint = document.getElementById('paradigm-match-hint');
+ if (!customerId) {
+ // 恢复全部产品
+ productSel.innerHTML = '';
+ paradigmProducts.forEach(p => {
+ const opt = document.createElement('option');
+ opt.value = p.id;
+ opt.textContent = `${p.name} ${p.spec || ''}`;
+ productSel.appendChild(opt);
+ });
+ hint.textContent = '';
+ return;
+ }
+ const customer = paradigmCustomers.find(c => String(c.id) === customerId);
+ if (!customer) return;
+ const cats = (customer.product_categories || '').split(', ').filter(x => x);
+ if (cats.length > 0) {
+ // 筛选匹配产品线的产品
+ const matched = paradigmProducts.filter(p => cats.includes(p.category));
+ productSel.innerHTML = '';
+ matched.forEach(p => {
+ const opt = document.createElement('option');
+ opt.value = p.id;
+ opt.textContent = `${p.name} ${p.spec || ''}`;
+ productSel.appendChild(opt);
+ });
+ hint.textContent = `客户关联产品线: ${cats.join('、')},已筛选 ${matched.length} 个匹配产品`;
+ } else {
+ // 无关联产品线,显示全部
+ productSel.innerHTML = '';
+ paradigmProducts.forEach(p => {
+ const opt = document.createElement('option');
+ opt.value = p.id;
+ opt.textContent = `${p.name} ${p.spec || ''}`;
+ productSel.appendChild(opt);
+ });
+ hint.textContent = '该客户未关联产品线,显示全部产品';
+ }
+}
+
+/** 选择产品时,筛选匹配产品线的客户 */
+function onParadigmProductChange() {
+ const productId = document.getElementById('paradigm-product-select').value;
+ const customerSel = document.getElementById('paradigm-customer-select');
+ const hint = document.getElementById('paradigm-match-hint');
+ if (!productId) {
+ // 恢复全部客户
+ customerSel.innerHTML = '';
+ paradigmCustomers.forEach(c => {
+ const opt = document.createElement('option');
+ opt.value = c.id;
+ opt.textContent = `${c.customer_name} (${c.salesperson_name}, ${c.stage})`;
+ customerSel.appendChild(opt);
+ });
+ hint.textContent = '';
+ return;
+ }
+ const product = paradigmProducts.find(p => String(p.id) === productId);
+ if (!product) return;
+ // 筛选产品线匹配的客户
+ const matched = paradigmCustomers.filter(c => {
+ const cats = (c.product_categories || '').split(', ').filter(x => x);
+ return cats.includes(product.category);
+ });
+ if (matched.length > 0) {
+ customerSel.innerHTML = '';
+ matched.forEach(c => {
+ const opt = document.createElement('option');
+ opt.value = c.id;
+ opt.textContent = `${c.customer_name} (${c.salesperson_name}, ${c.stage})`;
+ customerSel.appendChild(opt);
+ });
+ hint.textContent = `产品线: ${product.category},已筛选 ${matched.length} 个匹配客户`;
+ } else {
+ customerSel.innerHTML = '';
+ paradigmCustomers.forEach(c => {
+ const opt = document.createElement('option');
+ opt.value = c.id;
+ opt.textContent = `${c.customer_name} (${c.salesperson_name}, ${c.stage})`;
+ customerSel.appendChild(opt);
+ });
+ hint.textContent = `产品线: ${product.category},无匹配客户,显示全部`;
}
}