fix(ui): no raw JSON on Training list, pain no longer [object Object], add Chat button on GroupEdit, drop facebook/line from persona

- list_groups returns lightweight summaries (id/title/status/persona_count + product) —
  never the full personas array, so Training no longer shows a long JSON blob.
- PersonaForm toLines extracts .description/.text from pain/objection objects (fixes
  '[object Object]'); save re-wraps pains as {description}.
- GroupEdit persona cards get a 'แชท' (MessageSquare) button so you can start chatting
  right where you land after creating (chat was only on Personas page before).
- channel: default neutral 'social', removed facebook/line validation + removed from
  revealable_view (scenario now sets the tone, not fb/line).
All 9 backend suites pass. Rebuilt dist.
This commit is contained in:
Macky
2026-08-09 11:15:13 +07:00
parent f8719d77e3
commit cb2642a659
29 changed files with 69 additions and 42 deletions

View File

@@ -163,7 +163,21 @@ def list_groups():
for g in visible
if not g.get("owner_user_id") or g.get("owner_user_id") == actor["id"]
]
return jsonify({"groups": visible})
# Lightweight summaries only — never send the full personas/sales_kit/report to a
# list view (huge, heavy, leaks the recipe). Training shows title + persona count.
summaries = []
for g in visible:
personas = g.get("personas") or []
summaries.append({
"id": g.get("id"),
"title": g.get("title", ""),
"status": g.get("status", "draft"),
"channel": g.get("channel"),
"org_id": g.get("org_id"),
"persona_count": len(personas),
"input": {"product": (g.get("input") or {}).get("product", "")},
})
return jsonify({"groups": summaries})
@groups_bp.post("/<gid>/analyze")

View File

@@ -23,7 +23,7 @@ def ensure_persona_shape(p: dict[str, Any]) -> dict[str, Any]:
"name": p.get("name", ""),
"tier": p.get("tier", p.get("intent_tier", "B")),
"initiation_mode": p.get("initiation_mode", "customer"), # customer | seller
"channel": p.get("channel", "facebook"), # facebook | line
"channel": p.get("channel", "social"), # internal tone hint (scenario overrides)
# revealable
"profession": p.get("profession", ""),
"age_group": p.get("age_group", ""),
@@ -47,13 +47,11 @@ def ensure_persona_shape(p: dict[str, Any]) -> dict[str, Any]:
"tolerance": p.get("tolerance", 3), # misses before this persona walks away (temper)
"notes": p.get("notes", ""),
}
# validate
# validation
if base["tier"] not in DEFAULT_TIERS:
base["tier"] = "B"
if base["initiation_mode"] not in ("customer", "seller"):
base["initiation_mode"] = "customer"
if base["channel"] not in ("facebook", "line"):
base["channel"] = "facebook"
return base
@@ -63,7 +61,6 @@ def revealable_view(p: dict[str, Any]) -> dict[str, Any]:
"id": p.get("id"),
"name": p.get("name"),
"tier": p.get("tier"),
"channel": p.get("channel"),
"initiation_mode": p.get("initiation_mode"),
"profession": p.get("profession"),
"age_group": p.get("age_group"),