feat(chat): scenario-based training — choice of channel/Situation + realistic per-turn evaluation

Backend:
- Channel/initiation now driven by a SCENARIO chosen at chat start, not baked into the
  persona: social (customer opens), f2f_call (seller must open, proactive), recontact
  (customer re-contacts after 1-3 months).
- /chat/start accepts {scenario}; session stores scenario + internal{turns,score}.
- persona_reply takes scenario + adapts tone; system-role transcript entries are fed to
  the persona as hidden scene notes.
- JUDGE updated for realism: good response can WIN even in hard/tough-text scenarios;
  long/no-close chats (turns >~12) lose; pushy/ignoring-need loses. Efficiency rewarded.

Frontend:
- Scenario picker before chat (choose Social / Face-to-face-call / Re-contact).
- Chat thread renders role=system as a centered time-lapse/scene note.
- Choose-scenario i18n (EN+TH).
Rebuilt dist.
This commit is contained in:
Macky
2026-08-08 11:27:05 +07:00
parent 8670addcc3
commit bd6a7ffa32
27 changed files with 314 additions and 110 deletions

View File

@@ -79,7 +79,8 @@ def main():
assert r.status_code == 200, r.get_json()
session = r.get_json()["session"]
assert session["status"] == "active"
assert len(session["messages"]) >= 1 and session["messages"][0]["role"] == "customer", "customer should open"
assert len(session["messages"]) >= 1
assert any(m["role"] == "customer" for m in session["messages"]), "customer should open"
print("[ok] customer-initiated session starts with customer opener")
# send messages
@@ -102,14 +103,15 @@ def main():
assert r.status_code == 400, r.get_json()
print("[ok] one-shot enforced (cannot re-chat same persona)")
# seller-initiated persona -> session starts WITHOUT opener (task for seller)
# seller-facing persona + f2f_call scenario -> session starts WITHOUT opener (seller must open)
sel = next(p for p in personas if p["initiation_mode"] == "seller")
r = client.post(f"/api/chat/{gid}/personas/{sel['id']}/chat/start", headers=UH)
r = client.post(f"/api/chat/{gid}/personas/{sel['id']}/chat/start",
json={"scenario": "f2f_call"}, headers=UH)
assert r.status_code == 200, r.get_json()
s2 = r.get_json()["session"]
assert "task" in r.get_json() or "initiation_mode" in r.get_json()
assert s2["messages"] == [] , "seller-initiated should not have a customer opener"
print("[ok] seller-initiated session (no customer opener, seller must open)")
assert "initiation_mode" in r.get_json()
assert not any(m["role"] == "customer" for m in s2["messages"]), "f2f_call: no customer opener (seller must open)"
print("[ok] seller-first (f2f/call) session no customer opener, seller must open")
# board
r = client.get("/api/me/board", headers=UH)