86 Commits

Author SHA1 Message Date
Macky
a95b7d3c74 feat(debrief): surface buyer pains as pain insight instead of the persona reveal grid 2026-10-03 11:58:31 +07:00
Macky
d18fbfa608 fix(debrief): raise public coaching budget 1200→6000 so loss coaching stops failing closed 2026-10-03 11:58:31 +07:00
Macky
9df2938557 fix(llm): log raw provider output on JSON failure, raise judge budget to 6000
- complete_json(): log model/max_tokens + raw provider text (1500 cap) when
  json.loads fails, so a truncated judge reply is diagnosable from the log
- complete()/complete_conversation(): log reasoning_content head on empty
  content (hidden reasoning starves the shared max_tokens cap)
- simulator.judge(): max_tokens 2000 -> 6000 (server accepts 8000)
- tests: pin judge budget; new test_llm_failure_logging.py pins the logging
  (autouse fixture restores app.llm logger disabled by alembic fileConfig)

547 passed. Incident #4 (500 char 125).
2026-10-02 22:30:28 +07:00
Macky
903602a159 docs: plan — incident #3 closed, 0f34af7 deployed 2026-10-02 20:10:02 +07:00
Macky
0f34af71d1 fix(llm): always send enable_thinking=true — hidden reasoning was written inline into content
The llama.cpp endpoint writes the model's hidden reasoning INLINE into
content unless enable_thinking=true is sent (which relocates it to the
separate reasoning_content field). The app sent the field absent, so:
- complete_json got corrupted JSON -> live 500 'Unterminated string char 143'
- persona JSON generation failed (same root cause, earlier incident)
- persona replies were bloated with the reasoning chain

Send the flag on all three call paths (complete/complete_json/
complete_conversation). thinking param retained for API compat, now a
no-op on this provider (always separated — the only correct mode here).

- tests/test_llm_enable_thinking.py pins the flag on all three paths
- full suite: 543 passed; independent reviewer PASS (22 focused passed,
  single OpenAI client in the app, both fixed sites confirmed, no stale
  tests)
- cleanup stale comments that encoded the backwards understanding
  (endpoint 'ignores enable_thinking' — it honors it; that misconception
  is what caused this incident)
2026-10-02 20:09:31 +07:00
Macky
f995280483 docs: handoff — budget fix pushed c5553eb, auto-deployed 2026-10-02 19:02:26 +07:00
Macky
c5553eb9a6 fix(llm): raise persona-reply/judge token budgets over hidden reasoning
The Qwen3.8 GGUF reasons internally before answering and the llama.cpp
endpoint ignores enable_thinking — hidden reasoning consumed the whole
400-token persona_reply budget (HTTP 200, content='') -> LLMError -> live
500 'LLM service unavailable'. The 800-token judge JSON had the same
starve and silently degraded to 'pending', so customers who decided to
buy never closed the session.

- persona_reply 400 -> 1200, evaluate_turn 800 -> 1600
- tests/test_llm_budgets.py pins the floors so a regression to the old
  values fails loudly
- full suite: 540 passed; independent review PASS (truncation-safe:
  partial protocol JSON is rejected, never rendered)
2026-10-02 19:01:33 +07:00
Macky
ce405a6b6b docs: mark Gitea outage resolved (d17462e pushed) 2026-10-02 17:13:26 +07:00
Macky
d17462edc4 fix: retry transient LLM failures to survive shared-server queue, log 500 detail
- llm.py: _create() wrapper retries APIConnectionError (incl. timeout) up to
  LLM_MAX_ATTEMPTS=3 with LLM_RETRY_DELAY_SECONDS=5s backoff; 4xx still fails
  fast. The local LLM server is shared with Hermes and serializes requests, so
  a request can be dropped by the client timeout while queued — chat/send was
  500ing on that.
- helpers.py: internal_error() now logs full exception detail server-side
  (was only the type name), so 500s are diagnosable; client message unchanged.
- config.py: LLM_MAX_ATTEMPTS / LLM_RETRY_DELAY_SECONDS env knobs.
- tests: test_llm_retry_queue (transient retried, 4xx not retried, no retry on
  success), test_internal_error_logging (detail in log, hidden from client,
  order-independent via explicit handler + logger re-enable).
2026-10-02 17:04:55 +07:00
Macky
f63f1867f5 feat: trial follow-up drills, persona progress UI, LLM timeout 180s
- social trial: silent/gone follow-up outcomes with localized notice
  variants; gone is always a loss with possibility-framed debrief
- gone marker persists across judge outages; retries stay on the
  deterministic loss path (no roleplay reopen)
- bounded analysis_progress persisted during persona generation,
  exposed via safe serializer, polled + rendered as accessible UI
- LLM HTTP timeout 105s -> 180s for Qwen 3.8 (enable_thinking off)
- demo-account test clock made relative to current UTC
- regression coverage: follow-up branches, judge-failure retry,
  progress callback, timeout contract, progress serializer
- independent review finding verified stale against final tree

Tests: 532 backend, 29 frontend, prod build green
2026-10-02 12:53:59 +07:00
Macky
8c89b17f64 feat(follow-up): chat ends only on win/loss — try rolls a follow-up drill
`try` (customer will trial first) no longer auto-closes the session.
A random follow-up event is rolled so the trainee practices the follow-up:
  1. customer goes silent (~2 days) — trainee must ทัก first, then rolls:
     gone for good (instant loss + judge debrief) or hesitant (re-engages);
  2. customer comes back on their own with a post-trial message.

Only buy/walk/gone-for-good ever close the session. New notes/returners are
in _NOTES and added to _SAFE_SYSTEM_MESSAGES; the followup phase is stripped
from LLM prompts by the _safe_roleplay_internal allowlist (no latent leak).

Tests: 6 new (try->silent, silent->gone, silent->hesitant, try->returned,
buy closes, walk closes) with deterministic StubRandom + LLM-call counters
asserting follow-up turns skip persona_reply/evaluate_turn.
2026-10-02 08:37:27 +07:00
Macky
a4aae466ea fix(chat): seed customer-initiated chats with the persona's real opener
Revert the over-redaction from 3c22d88: a customer-initiated
session (social / lead who messages in) now greets with the
persona's generated opener — a realistic 'สวัสดี สนใจ ขอถามราคา'
line — instead of the static 'ลูกค้าเริ่มต้นบทสนทนาแล้ว'
placeholder. The opener is spoken public dialogue by design
(OPENER RULE keeps pains/budget/personality out of it), so
remove the redact_initial_customer strip from serialize_session.

Phone/f2f (seller-initiated) is unchanged: no customer line,
the trainee opens the call. Untrusted system-note filtering and
internal-state/provider-path stripping are untouched.

Tests: 5 redaction regressions flipped to the new contract.
517 passed (3 pre-existing demo-date failures, unrelated).
2026-10-01 22:54:30 +07:00
Macky
a7abc724be fix(analysis): log exceptions on analysis failure — was silently swallowed 2026-10-01 20:56:16 +07:00
Macky
df0428d7d2 feat: async analyze + frontend polling, quota enforcement, persona tier batching
- analyze returns 202 immediately; frontend polls group status (3s interval, 30min cap)
- quota system: atomic check-and-consume, trial/team/enterprise plans, 402 on exceed
- persona generation split into tier batches (Cloudflare 120s limit safe)
- LLM client: 105s timeout, thinking param for reasoning models
- tests: quota suite (4), persona cardinality/initiation updates
2026-10-01 15:19:46 +07:00
Macky
1ad21cc8f1 test: cover product delete cascade (group + own sessions) keeping other-org sessions
Proves DELETE /api/groups/<gid> removes the product, its personas (stored on the
group), its own sessions, and uploads, while sessions of an unrelated product/
org survive. Guards the cascade against future regressions.
2026-08-26 14:04:47 +07:00
Macky
47f92b48bf fix(chat): remove manual finish button — sale auto-closes when the customer decides
The realistic sales simulation must not offer a "Finish practice / จบการฝึก"
button, because the chat is meant to auto-close on buy/walk/try (as originally
decided in ffb7cdd). The finish button and its chatFinish handler were
reintroduced in 3c22d88 and are now removed; the composer holds only Send and the
debrief renders automatically once the session finishes. Regression test asserts
no finish/summary button is rendered.
2026-08-26 14:04:47 +07:00
Macky
0903b19691 fix(ui): capitalize Persona in EN+TH UI copy and drop forced-lowercase count label
Use "Persona"/"Personas" (capital P) as the feature term across the English and
Thai UI instead of lowercase "persona". Thai now reads "Persona" with a space
separator in mixed Thai-English sentences (e.g. "เลือก Persona เพื่อฝึก",
"จัดการ Persona", "บันทึก Persona"). Remove .toLowerCase() on the persona count
label in Training so it renders "Personas"/"Persona" consistently.
2026-08-25 21:05:13 +07:00
Macky
fde094e233 docs: capitalize Persona/Personas for the product feature in review docs
Feature-word 'persona'/'personas' now reads 'Persona'/'Personas' (capital P)
across the current review checkpoint docs, matching the UI terminology. Code
identifiers, API paths, and schema fields stay lowercase.
2026-08-25 20:37:54 +07:00
Macky
6a2e6a326f fix: restore legacy training data hidden by new visibility schema (migrate-on-read)
Records written before the visibility field existed carry none; the new
fail-closed authorization treated missing visibility as invalid, making every
legacy group unlistable and unreadable. Add resolved_visibility(group) that
derives effective visibility for legacy records only (owner present => private,
absent => public), leaves explicit-malformed visibility fail-closed (None), and
never derives demo/hidden. Apply it at every list, authorization, chat, and
analytics boundary while keeping demo and hidden-preview paths raw and
owner_user_id-based private isolation intact. No persisted data is rewritten.

Backend full suite passes 517; frontend 26/26; production build passes.
2026-08-25 08:59:22 +07:00
Macky
3c22d88bcd feat: demo SaaS + training flow security hardening (8/8 review gate passed)
- Demo accounts: super_admin-only provisioning into isolated DEMO_ORG_ID tenant,
  30-day UTC trial on first login, revocable, one-time credential delivery via
  optional SES/webhook (never persisted). Adds boto3 dependency.
- Analytics/report/export/privacy: shared bounded scan budget across users/groups/
  sessions, tenant-consistent session/user/group joins, scalar-only CSV export
  (no nested persisted-value stringification).
- Ownership/tenant isolation: canonical owner-tenant predicate for list/read/chat;
  client sees is_owned only, never owner_user_id.
- Lifecycle/races: status transition validation, analyzing is an in-progress gate
  (no duplicate reanalysis), structured-ready publication, stale-variant revalidation.
- Auth/setup/consent/JWT/OAuth/config: fail-closed consent, bounded JWT lifetime,
  provider-subject atomic OAuth identity, repeated-secret rejection, strict Persona
  trait validation.
- Chat/session/privacy: pre-seller opener redaction, corrupt-session recovery,
  role-aware completed-chat dashboard routing.
- Frontend: Training→product→personas→practice flow, demo/role/demo guards,
  is_owned-based ownership display, 320×568 and 500×768 responsive E2E.
- 8 independent exact-five-key review scopes passed; backend 509, frontend 26,
  production build 1775 modules, isolated E2E 15.
2026-08-25 06:39:06 +07:00
Macky
8a632b5e6a docs: record successful production deploy of UX/SAAS redesign 2026-08-21 12:30:21 +07:00
Macky
9fd748154d feat: UX/SAAS 12-point redesign
- auth: self-registration (role=user); first-created-user becomes super_admin
- roles: super_admin may promote others; regular admin cannot see super_admin accounts
- products: user-created private groups; admin 'สินค้าขององค์กร' (shared) with hidden/public; users can create groups
- analytics: team + per-user weak areas, close-rate-by-difficulty buckets, 30-day default, weekly trend, trainee table, active users; dashboard redesigned
- files: docx + xlsx upload support (python-docx + openpyxl)
- ui: tabs การฝึก→ผลการฝึก→ภาพรวม; admin lands on ภาพรวม / user on การฝึก; guide in topbar
- consolidate: weak-areas merged into Results (10/page), my-personas merged into Training
- copy: บุคคลต้นแบบ→persona everywhere; clearer add-product form (A/B/C, upload-or-fill)
- report: remove ดูรายงาน UI entry (endpoint kept)

Backend 348 tests pass; frontend build + vitest clean.
2026-08-21 12:28:22 +07:00
Macky
2950ffad89 docs: record successful production deploy of OAuth + website removal 2026-08-20 11:39:48 +07:00
Macky
d6e7cffc84 feat(auth): Google + Facebook OAuth login/register
Public social signup into OAUTH_DEFAULT_ORG (role user, seat-checked);
email-match links existing active user instead of duplicating. Server-side
provider token validation via stdlib urllib only (no new dep): Google
tokeninfo (aud + email_verified) and Facebook app/debug-token/me (is_valid,
app_id, me.id==user_id). Fail-closed when creds unconfigured, rate-limited
per-IP + per-email, /oauth/config leaks no secrets. Frontend: login buttons
(only enabled providers), GSI + FB SDK on-demand, monochrome glyphs, TH/EN.

Login page shows social buttons only when backend reports provider enabled.

348 backend tests pass (337 + 11 new OAuth), frontend build + 4/4 unit
clean, manual security review PASS. Not pushed (push auto-deploys).
2026-08-20 10:57:04 +07:00
Macky
74de0d4d4f chore(website): remove marketing site from app repo (moved to separate project ~/Gitea/Sales Trainer Website)
Per owner: this repo should contain only the app. The marketing landing
site now lives in its own git repo (Sales Trainer Website); not pushed.
2026-08-20 09:25:20 +07:00
Macky
ece187be44 docs: record successful production deploy of UX redesign + marketing site
Verified live: backend booted (JWT_SECRET set, no crash-loop), new bundle
hashes + new Login design served, website/ in repo (not yet deployed
publicly; pricing placeholders pending).
2026-08-19 18:34:41 +07:00
Macky
c263cc81df feat(ui): app UX/UI redesign + marketing landing site
App (Vue SPA) — 100% presentational, global design-token rebuild in
style.css + polished App/Login/Chat/MyBoard/Personas/Setup/Training.
No logic/API/route/i18n changes (verified: build clean, 4/4 vitest,
independent review PASS 5/5).

website/ — new self-contained static marketing landing site (TH-primary
+ EN toggle, CSS chat mockup, Hallmark theme), verified responsive at
320-768 (no horizontal scroll). Pricing uses placeholders pending owner.

Docs: engineering-log 2026-08-19 + HANDOFF updated.

NOT pushed (push to main auto-deploys to EasyPanel production;
operator approval + JWT_SECRET check required before deploy).
2026-08-19 15:46:21 +07:00
Macky
4bff7db098 fix(persona): openers must ASK or say สนใจ — never 'ลองดู/ลอง/แค่มาดู'
- persona_prompts OPENER RULE: openers must be a direct question OR declare
  interest ('สนใจ/สอบถาม'); explicitly forbid 'ลองดู / ลอง / มาลอง / แค่มาดู /
  กดดู / แวะดู' (trying/browsing) phrasing which Thai customers don't use
- fix_openers.py: rewrite browse/try openers into a polite natural interest
  opener ('สวัสดีครับ/ค่ะ สนใจสอบถามสินค้าครับ ขอรายละเอียดได้ไหม'), leaving
  openers that already ask a question or say สนใจ untouched
2026-08-19 10:19:29 +07:00
Macky
7fcef45649 fix(persona): forbid dismissive openers + fix script for '..' and negative tails
- persona_prompts OPENER RULE: never open with dots/ellipsis and never end
  an opener by dismissing/belittling the product (but ไม่คิดจะซื้อ, แค่มาดูเล่น,
  ก็แค่มาเมิน, ไม่ได้จะซื้อ...) — Thai openers stay politely interested
- fix_openers.py: strip leading dots("..") and remove trailing dismissive
  clauses ('ตามไม่คิดจะซื้อหรอกนะ' etc.) so existing personas are normalized
  to a natural interest opener; update docstring
2026-08-19 10:12:56 +07:00
Macky
165cd840cf fix(script): bootstrap sys.path so 'app' imports regardless of cwd in fix_openers 2026-08-19 10:05:45 +07:00
Macky
9b79e0c53e feat(script): one-time fix_existing personas' openers to natural Thai
Deterministic, conservative: only rewrites openers that lead with a casual
interjection (เฮ้/เอ้/เอ่อ/เอ๊ะ/อืม/เออ/นี่ๆ/ว่าไง/hey/hi/hiya/yo) or a bare
politeness word (ครับ/ค่ะ). Leaves natural Thai and English openers untouched.
Prints before -> after. Run in the container console:
  cd /app/backend && python3 scripts/fix_openers.py
2026-08-19 09:30:35 +07:00
Macky
07e80be952 fix(persona): natural Thai openers - 'สวัสดีครับ/ค่ะ' + direct question, no 'เฮ้/เอ้'
- persona_prompts OPENER RULE: mandate polite Thai openers that start
  with สวัสดีครับ/ค่ะ and go straight to the question or 'สนใจ/สอบถาม';
  explicitly forbid casual English fillers (เฮ้/เอ้/เอ่อ/hey) and
  singsong slang for Thai
- chat_routes: default opener fallback is now locale-aware - Thai locale
  uses 'สวัสดีครับ สนใจสินค้าของคุณครับ...' instead of the English one
2026-08-19 09:26:19 +07:00
Macky
4b5a14079b [verified] all roles train for real: remove preview, 1 persona = 1 chat per user
- listPersonas: compute per-user my_outcome for ALL roles; always set the
  key (default 'not_tried') so the all-roles UI never sees undefined
  (fixes global super_admin without org showing every persona as trained)
- start_session/mode lookups (send/finish/resume/authorize): no preview
  mode; every role creates one-shot 'trainee' sessions. The one-shot lock
  (1 persona chat per user; many users per persona) now applies to all roles
- Chat.vue: always start in trainee mode, remove preview label
- Personas.vue: unified branches - show 'แชท' when untrained, 'สรุปผล'+variant
  when trained, for all roles
- Rewrite test_admin_preview.py to assert the new no-preview one-shot behavior
2026-08-19 08:32:33 +07:00
Macky
ab5fff0bd9 [verified] IP-protect pain (hide from all roles) + persona summary button + topbar user dropdown
- Backend: strip pain/painProgress/revealed_persona.pains from debrief
  serializer and remove pain/initialPainFit from admin report so the
  coaching formula never leaks to any user-facing role (persona keeps
  pains internally to drive the judge/training)
- Fix [object Object] array-of-objects rendering in Chat/SessionDetail
- Personas: trained persona shows 'สรุปผล/Summary' button -> chat page
  with past result (chat already loads finished session)
- Top bar: username dropdown containing Settings + Logout (was separate
  logout button); variant-from-base already preserves tier/difficulty
- Updated debrief allowlist tests to reflect new redaction
2026-08-19 07:26:21 +07:00
Macky
711e058b24 [verified] fix debrief pain display ([object Object]) + add 'create persona from this one' button
- formatValue(): render arrays of objects (e.g. pains) as readable text
  instead of '[object Object], [object Object]'
- SessionDetail: new 'สร้างบุคคลต้นแบบจากต้นแบบนี้' button calls the
  existing group persona-variant endpoint so trainees/orders can regenerate
  a persona based on the revealed one after a session
2026-08-18 22:01:27 +07:00
Macky
19e9fd2328 docs: record JWT_SECRET boot-crash root cause for 2026-08-18 deploy 2026-08-18 14:41:00 +07:00
Macky
5febcc83c4 docs: note deploy-not-live follow-up for 2026-08-18 UX fixes (force webhook re-trigger) 2026-08-18 12:04:11 +07:00
Macky
ffb7cdda31 [verified] fix live-QA UX: no JSON leak in chat, greeting openers (wrong_text cools off post-first-reply), auto-close+summarize on buy/walk/try, remove Finish button 2026-08-18 09:42:05 +07:00
Macky
2c40fc7502 feat(import): commit S4.4 JSON→PostgreSQL importer + API error-handler hardening
Re-verified staged increment from a clean requirements.lock.txt venv:
- 330 backend tests pass (17/17 in new error_handlers + json_import tests)
- compileall + frontend npm build clean
- git diff --check clean; no secrets in diff
- importer CLI dry-run bootstrap works

Includes JSON HTTPException handler under /api/* and parse-safe static 404
via abort. JSON stores remain runtime-authoritative; production operation
still gated behind operator approval.
2026-08-16 07:42:32 +07:00
Macky
dbfce9aa54 [verified] harden Sales Trainer and add PostgreSQL foundation 2026-08-16 02:41:10 +07:00
Macky
6811dc1db9 docs: refresh HANDOFF + add 2026-08-09 engineering-log entry (idea-flow, live QA, LLM-judge decisions, persona variant) 2026-08-09 19:18:10 +07:00
Macky
c92400b195 refactor(chat): decide buy/walk by per-turn LLM judge (not fixed keywords)
Removed the fixed-value text detector. persona_reply no longer forces JSON meta; instead a
per-turn evaluate_turn() calls the judge LLM after every customer reply to read the persona's
current mood + whether it has decided (buy/walk/pending) + score_delta + reason. send_message
consumes that context-based decision to (a) end the chat as won/lost and (b) move the score.

This is what the user asked: the system evaluates EVERY turn and decides at the moment it's
truly committed — not keyword matching (so 'ซื้อไม่ไหว แต่ว่ามีผ่อนไหม?' stays pending).
Mock updated: judge returns buy on first send (keeps E2E deterministic). 11/11 suites pass.
2026-08-09 13:19:15 +07:00
Macky
94e75238a3 fix(chat): resume unfinished session without re-picking scenario; detect win/loss from real-LLM text
- start_session now RESUMES an existing ACTIVE session for the persona instead of creating
  a new one / forcing re-pick of the scenario (keep original scenario+messages).
- send_message falls back to a text-based decision detector (_detect_customer_decision)
  because real LLMs rarely emit structured meta.decision — so a customer who says
  'ซื้อไม่ไหว'/'no thanks' now actually ENDS the chat as lost (was stuck active forever).
  Fragments handled in TH + EN; buy + walk.
All 11 backend suites pass. Rebuilt dist.
2026-08-09 13:12:08 +07:00
Macky
10c7d01236 feat(personas): 'create persona from this persona' (variant) — practice the same challenge, new identity
Each persona is one-shot (chat once = win/lose locked). To keep training repeatable:
- New endpoint POST /api/groups/<gid>/personas/<pid>/variant creates a NEW persona that is
  a fresh incarnation of the source: LOCKS pain points, objections, negotiation levers,
  tolerance, special/recontact, goal, budget, difficulty, tier, product_context — but VARYS
  name/profession/age/location/background/personality/opener so it isn't an identical copy.
- Added to the same group as a distinct persona (fresh not_tried, so chat-able again).
- UI: on the Personas page, a finished (won/lost) persona gets a
  'สร้างบุคคลต้นแบบจากต้นแบบนี้' button; reload shows the variant.
All 10 backend suites pass. Rebuilt dist.
2026-08-09 12:43:00 +07:00
Macky
8771438aef fix(scenario): recontact chats normally first then re-engages mid-chat; channel default=social; remove 'add more' button (15 personas auto)
- recontact persona now opens as a NORMAL customer (no 'I asked before' in the opening);
  the mid-chat time-lapse system note (turn 2) makes them re-engage warmer instead.
- channel default changed facebook->social everywhere (create/analyze/store/simulator/me).
- 15 personas auto-generated (TARGET=15); removed the 'สร้างบุคคลต้นแบบเพิ่มเติม' button/guide.
All 9 backend suites pass. Rebuilt dist.
2026-08-09 12:37:43 +07:00
Macky
2b414d13b7 fix(analyze): don't hard-fail when LLM generates <15 personas (retry + accept)
Live test found: deepseek returned 14/15 personas -> whole analyze 500'd, breaking the
'กดสร้าง -> auto-analyze' flow. Now generate() retries up to 3x with a nudge, and accepts
a short result (>=8 personas) instead of crashing — admin can top up the rest with
'สร้างบุคคลต้นแบบเพิ่มเติม'. Also default tolerance/recontact on generated personas.
All backend suites pass.
2026-08-09 12:23:15 +07:00
Macky
479d77757f fix(chat): map customer/seller roles for OpenAI-compat LLM; re-contact = mid-chat time-lapse
- llm.complete_conversation now maps internal roles (customer->assistant, seller->user,
  system->system) before the API call — fixes 'Unknown role: customer' (501).
- Re-contact personality: the customer now chats normally, at turn 2 goes quiet and a
  system time-lapse note is shown ('⏳ ผ่านไป 2-3 สัปดาห์...'), then re-engages warmer —
  instead of 'pretending you asked before' at start. Driven by persona.recontact trait.
All 9 backend suites pass. Rebuilt dist.
2026-08-09 11:25:50 +07:00
Macky
fd2cae9e62 refactor(scenario): only 2 scenarios (social / face-to-face); 're-contact' becomes a persona trait
- Scenario picker now has only social + f2f_call (removed recontact) in backend
  _scenarios, start_session validation, and Chat.vue picker.
- 'ลูกค้ากลับมาติดต่อ' is no longer a scenario: it's now a PERSONA trait. Persona prompt
  generates ~1-in-4 personas with recontact=true (asked before, now returns warmer/ready);
  store shape gets recontact field; simulator injects a re-contact note into the persona's
  system prompt so it plays as a returning customer naturally.
- test_scenario updated: unknown scenario defaults to social; recontact shown as a
  generated persona trait.
All 9 backend suites pass. Rebuilt dist.
2026-08-09 11:21:34 +07:00
Macky
cb2642a659 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.
2026-08-09 11:15:13 +07:00
Macky
f8719d77e3 ux(flow): remove facebook/line channel everywhere (scenario chosen at chat); clarify 'how to start chatting'
- Removed channel + initiation displays: Training card, Personas card, GroupEdit card,
  Chat header, and the channel select in PersonaForm (scenario now determines who opens).
- Clarified the start-training flow: GroupEdit guide now says go to the Training tab ->
  pick product -> pick a customer -> Chat -> choose scenario -> talk; Personas guide
  spells out the Chat + scenario step.
Rebuilt dist.
2026-08-09 11:08:14 +07:00