Macky ff0f680090 [verified] Security hardening + UX/UI polish
Security (requesting-code-review pipeline + independent reviewer):
- Fix path traversal on file upload (basename sanitize + resolve-containment)
- Fix IDOR: org + owner scoping on all group/chat routes (_authorize_group/_get_owned_group),
  hide other users' personal groups in listings
- Remove XSS via v-html in Chat task (text interpolation)
- Add test_security.py (traversal + cross-user denial) — all pass

UX/UI (ui-ux-pro-max + frontend-dev-verification):
- Global: focus rings, 44px touch targets, hover/press transitions, input focus glow,
  prefers-reduced-motion, skeleton loaders, empty states, back links, spinner
- Login: password toggle, autocomplete, spinner, disabled-when-empty
- Cards lift on hover; dashboard skeleton + empty state; analyze button spinner

All backend tests pass (m0/m1/routes/security/e2e); frontend builds; served SPA verified via curl.
2026-08-07 16:00:43 +07:00

🎯 Sales Trainer

A corporate, multi-user sales-training simulator. Admins upload/describe a product; the app analyzes it and generates 15 realistic customer personas (5 per buying-intent tier) with real, varied pains. Trainees chat one-on-one with each persona to practice closing a sale — customers negotiate, stall, and refuse unless their pain is genuinely resolved. Debrief reveals + coaches.

Built on patterns from the CrowdSight / MiroFish swarm engine and clean-room Hermes Brain & Tools plugin.


Features

  • Login + roles (no self-registration): super_admin / admin / user.
    • Admin builds & edits persona groups (product + 15 personas), hand-edits personas, sees analytics.
    • User (trainee) can't create — only selects a group and practices; sees own results.
  • Input via form (product / segment / description) and/or file upload (.pdf/.md/.txt). Product data is used mainly to extract pains; personas are reusable across similar products.
  • 15 personas (5 × tier A/B/C):
    • A = ready to buy · B = unsure · C = not interested but has pain (hardest).
    • Varied demographics, income, occupation, lifestyle, personality — consistent with product.
    • Pain variety (directly-solvable / partial / unrelated red-herring).
    • Negotiation levers (price, freebies, delivery time, scope, payment).
    • Initiation mode: customer opens OR seller must open the sale (outbound, e.g. insurance).
    • Channel: Facebook / LINE.
    • Special tier-C "wrong_text" persona (appears to buy, loses interest, but still has pain).
  • One-shot rule: a persona is chatted once per user (final); shared across other users.
  • Chat realism: all tiers can lose; everything negotiates; hidden internal signals + latent fields (pain/income/personality/budget) revealed only after the result.
  • Debrief: short summary + coaching (how to answer better on weak-score messages), scored by a separate judge LLM (no speed factor).
  • Training loop: win/lose board, weak-area analysis, and user-generated personas (weak-area "lock" or manual form).
  • Admin analytics: close rate, avg score, hardest personas.
  • EN + TH UI.

Quick start

Local (dev)

# backend (Python 3.11)
cd backend
uv venv --python 3.11 .venv
uv pip install -r requirements.txt --python .venv/bin/python
cp .env.example .env   # edit LLM keys + JWT_SECRET
uv run python run.py   # Flask on :5001

# frontend (separate terminal)
cd frontend
npm install
npm run dev            # Vite on :3000 -> proxies /api to :5001

The first run creates a default super-admin: admin@salestrainer.local / admin123 (change it!).

Docker / EasyPanel

cp .env.example .env   # set LLM_API_KEY + a strong JWT_SECRET
docker compose up -d   # single container serving frontend + API on :5001

LLM config

Any OpenAI-compatible endpoint (OpenAI, DeepSeek, or custom base URL):

LLM_PROVIDER=deepseek        # deepseek | openai | custom
LLM_BASE_URL=                # optional override
LLM_MODEL_NAME=deepseek-chat # optional override
LLM_API_KEY=sk-...

Architecture

frontend/   Vue 3 + Vite SPA (login, dashboard, group builder, personas, chat, debrief,
            gen-persona, weak-areas, analytics). Built to dist/ and served by Flask.
backend/    Flask API (JWT auth, roles, groups, analyzer, persona generator, chat simulator,
            judge, trainee loop, analytics). Filesystem JSON persistence (no external DB).
  app/services/  analyzer · persona_generator · simulator (+ judge) · report · trainee · groups · sessions
docs/PLAN.md   full design & decisions record
  • Storage: backend/data/ — JSON files per entity (users, orgs, groups, sessions, my_personas).
  • LLM calls: analyzer (sales kit + pain-fit), persona generator, persona chat, judge.

Tests

Run with the built-in deterministic mock LLM (no external key needed):

cd backend
uv run python scripts/test_m0.py      # auth/roles/no-self-registration
uv run python scripts/test_m1.py      # group create + failure handling + role visibility
uv run python scripts/test_routes.py  # all API routes registered
uv run python scripts/test_e2e.py     # full flow: analyze→personas→chat→debrief→one-shot→board→analytics

Real-model verification requires a live LLM_API_KEY in .env.


Default accounts

Role Email Password
super_admin admin@salestrainer.local admin123 (change after first login)

Admins create additional users (users/login has no self-registration).

Description
No description provided
Readme 2.3 MiB
Languages
Python 67%
Vue 24.3%
JavaScript 6%
CSS 2.1%
Dockerfile 0.5%
Other 0.1%