23 Commits

Author SHA1 Message Date
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
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
Macky
5b167b4546 feat(training): delete group button (admin only) on Training page
- Backend DELETE /api/groups/<gid> (admin, own-org enforced via _get_owned_group);
  cascades removal of the group's sessions. GroupStore.delete added.
- Training.vue: each group card gets a trash (Trash2 line icon) delete button shown only
  to admins (auth.isAdmin); confirm dialog; @click.stop so it doesn't navigate. Card
  restructured so the delete button is not inside the clickable router-link.
- test_user_journey: trainee cannot delete (403), admin deletes, group gone (404).
All suites pass. Rebuilt dist.
2026-08-09 10:20:10 +07:00
Macky
0b92430aab feat(idea): create group auto-generates personas; channel removed; product->product/idea
Per product-idea focus:
- GroupBuilder: 'สินค้า/บริการ/ไอเดีย' label; removed the channel select (channel is now
  chosen at chat time as a scenario, not at persona create). Create now auto-runs analyze
  so personas are generated immediately (no separate Analyze button).
- GroupEdit: analyze button becomes 'สร้างบุคคลต้นแบบเพิ่มเติม' which APPENDS more personas
  (backend analyze?append=true reuses sales kit + keeps existing instead of replacing).
- i18n product label updated.
- User-journey test asserts append adds personas (15->30) and existing kept.
All 9 backend suites pass. Rebuilt dist.
2026-08-09 10:12:40 +07:00
Macky
3d5c81fbd7 feat(saas): Phase 3 — plan/seats/active model, ToS consent, signed expiring export
P3a: org carries plan/seats/active/created_at; create_user enforces seats + rejects
inactive org; verify blocks login for inactive orgs; PATCH /api/admin/orgs (super_admin)
updates plan/seats/active with audit. Fixed verify swallowing its AuthError.
P3b: export/token issues a 5-min HMAC one-time CSV link; export accepts ?token=.
P3c: setup requires accepted_terms (consent stored); Setup.vue consent checkbox.
All 8 backend suites pass. Rebuilt dist.
2026-08-09 09:48:55 +07:00
Macky
e1d61e1e1e feat(ip): protect persona 'formula' — secret fields only super_admin sees/edits
IP protection so casual copying yields inferior results:
- SECRET_PERSONA_FIELDS (pains/objections/negotiation_levers/opener/tolerance +
  pain rootCause/resolutionConditions): only super_admin can view/edit them.
- list_personas/get_persona/update_persona/get_group strip these for role=admin (and
  hide sales_kit + pain-fit report from admins too).
- update_persona rejects admin attempts to set secret fields (403).
- PersonaForm hides the 'การขาย' recipe section for non-super-admin (shows locked note);
  auth.isSuperAdmin getter added.
Rebuilt dist. Added test_ip_protection.
2026-08-09 07:22:50 +07:00
Macky
a04bc2add8 fix(ui): widen login/setup forms so inputs don't truncate text; line icons
- Login card 360->460px (max-width:100%), wider inputs (46px, 15px, more padding),
  mobile side padding; eye-toggles -> lucide Eye/EyeOff line icons (no emoji).
- Setup card 380->460px (max-width:100%), wider inputs, Lock line icon instead of emoji.
Rebuilt dist.
2026-08-08 17:00:09 +07:00
Macky
b41bfee03b chore(ui): remove 4 orphaned views (Dashboard/GenPersona/MySessions/WeakAreas) + mobile polish
- Delete unused views not referenced by router/components (dead files from earlier builds).
- Mobile (640px/460px/380px): nav tabs wrap to a centered full-width row + hide brand at
  <380; logout shows icon-only; stat cards center when collapsing to one column
  (Moreminimore convention); chat bubbles/width handled.
Rebuilt dist.
2026-08-08 16:12:30 +07:00
Macky
15c60ae400 feat(i18n + export + guide): locale-aware scenarios/debrief; CSV export; how-to-use page
Backend:
- SCENARIOS localized (th/en): scenario preamble + persona tone adaptation follow the
  trainee's locale; start accepts {locale} and stores it on the session; send/auto-finish
  use it so debrief text (why/coaching/turning points) is in the active language.
- /api/analytics/export returns per-trainee finished-session CSV (admin).

Frontend:
- Scenario picker labels localize by i18n.locale.
- New /guide page (non-IT how-to), linked from Training.
- Analytics: date-filter bar uses line icon + 'Download CSV' button (fetch w/ auth).
Rebuilt dist.
2026-08-08 15:06:48 +07:00
Macky
aa2eb8dd37 feat(chat): persona decides to buy/walk — auto-finish + tolerance (temper) + resume
The conversation now ENDS when the persona makes a decision (option C), not when the
trainee clicks a button:
- persona_id replies carry {reply, decision(none/buy/walk), mood}; when decision is
  buy/walk the session auto-finishes (won/lost) with a debrief that reveals latent
  details + per-turn 'turning points'.
- personas have a tolerance (1-5, 'temper'): impatient personas walk away fast after
  poor answers (fed via internal.misses on mood<=-1); tough 'wrong text' cases can
  still be won by a strong, gentle response (judge realism).
- trainee 'Finish' button removed; if they leave mid-chat an active session is resumed
  via /chat/resume (continue, not restart). One-shot lock still enforced once decided.
- mock/tests updated: persona deciding buy -> send auto-finishes won.
Rebuilt dist.
2026-08-08 14:34:34 +07:00
Macky
bd6a7ffa32 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.
2026-08-08 11:27:05 +07:00
Macky
8670addcc3 ui(icons): replace colored emoji with modern single-color line icons (lucide-vue-next)
Add lucide-vue-next (stroke-based, single-color, currentColor) and swap emoji icons
across nav + all main views: Settings/LogOut (App), BarChart3/Users/Plus (Analytics),
LayoutDashboard (MyBoard), Sparkles/Users/Pencil (GroupEdit), Box/ListChecks/Paperclip/
PlusCircle/ArrowLeft (GroupBuilder), Target/ArrowLeft/Search (Chat/Personas/Training),
User/Lock/Globe (Settings), Users/UserPlus/Info (AdminUsers).
- Buttons become inline-flex for icon+text alignment.
- Icons inherit currentColor (single color, modern line style).
- Rebuild dist with lucide.
2026-08-08 10:55:45 +07:00
Macky
22d3e51980 ux(ui): beginner-friendly guidance across trainee/admin flows + fix AdminUsers to username
- Chat: add 'How to practice' guide card; render revealed persona as readable
  label:value grid instead of JSON.
- AdminUsers: switch create form to username (matches username login), add step guide,
  role explanation (user=ฝึก, admin=ดูแลระบบ).
- Analytics (admin overview) + MyBoard (personal): add plain-Thai explainer subtitle.
- Rebuild dist. Verified user create with username -> 201.
2026-08-08 10:34:24 +07:00
Macky
39d1aa04e4 ux(ui): readable persona form instead of raw JSON + step-by-step guides for non-IT users
- GroupEdit: replace raw JSON persona dump with a labeled, sectioned PersonaForm
  (ข้อมูลพื้นฐาน / ข้อมูลลูกค้า / การขาย / ช่องทาง) shown in a modal; admin edits by field.
- New reusable components/PersonaForm.vue.
- GroupBuilder + Personas: add friendly step-by-step guidance cards ('วิธีสร้าง',
  'วิธีฝึก') so admins/trainees know what to do at each step.
- Rebuild dist.
2026-08-08 10:24:53 +07:00
Macky
88892be1d9 i18n(th): professional, natural Thai; make Users button prominent
- Rewrite TH translations to professional/natural Thai (e.g. app 'ระบบฝึกทักษะการขาย'
  instead of 'ตัวฝึกขาย', 'บุคคลต้นแบบ' for persona, tiers 'พร้อมตัดสินใจซื้อ',
  'จุดอ่อนที่ควรฝึกเพิ่มเติม', 'ปิดการขายได้/ไม่ได้' for won/lost, etc.)
- Users button in admin overview now solid sky-blue (was faint .soft gray) so it's visible.
2026-08-08 08:01:08 +07:00
Macky
acba183dc1 style(ui): polish admin overview + close-rate progress bar; rebuild dist 2026-08-07 22:53:58 +07:00
Macky
b05907ae62 feat(ui): 3-tab UI per spec — admin dashboard + date filter, personal dashboard, training w/ difficulty, settings profile
Tab 1 Admin overview (/): aggregate stats + DATE FILTER (?from&to), hardest personas,
admin quick actions (Users / + Add product).
Tab 2 My dashboard (/my/board): per-user win/lose/session summary (works for admin too).
Tab 3 Training (/training): product list w/ status + persona count; admins see all groups
(draft->analyze/edit), trainees see ready->personas; persona list shows DIFFICULTY (1-5
stars); admins get a Manage-personas button.
Settings (/settings): profile (name/email) via new PATCH /api/auth/profile + change
password + language.
Backend: analytics date filter; profile endpoint; /api/me/board + /api/chat/sessions
opened to any authed user.
Rebuilt frontend/dist.
2026-08-07 22:35:49 +07:00
Macky
ac35be8906 feat(ui): 3-tab layout (Admin overview / My dashboard / Training) + Settings page
- App.vue: top tab bar (Admin overview [admin], My dashboard, Training) + Settings icon
- router: / = admin overview, /my/board, /training, /settings; non-admin '/' -> /my/board
- New views: Training.vue (pick ready product group -> personas), MyBoard.vue (win/lose +
  sessions summary), Settings.vue (change email/password, language)
- Backend: allow /api/me/board + /api/chat/sessions for any authenticated user (so the
  My dashboard tab works for admins too), not just 'user' role
- i18n EN/TH keys; rebuilt frontend/dist
Verified: build ok, /api/me/board + /api/chat/sessions + /api/groups all 200 for admin.
2026-08-07 22:07:16 +07:00
Macky
3bc23997e2 build(frontend): refresh committed dist (username login + setup flow)
The committed dist was stale (older build without email-resolving + username login).
Rebuild locally and commit the fresh SPA so EasyPanel serves the correct login/setup
flow. Verified fresh dist references username/must_setup/setup.
2026-08-07 21:00:43 +07:00
Macky
c19b23d44d fix(Docker): ship prebuilt frontend/dist, drop npm from image (removes 'vite not found')
The Vue SPA is now built locally and its frontend/dist/ committed (force-added).
Dockerfile is a single-stage runtime: copy prebuilt dist + python:3.11 + Flask.
No node/npm inside the image -> deterministic, no npm/esbuild postinstall flakiness.
To update UI: edit src, npm run build, commit frontend/dist/.
2026-08-07 19:46:20 +07:00