Sales Trainer v0.1: corporate sales-training simulator (Flask+Vue, 15 personas, chat simulator, judge, analytics)

- Auth/roles (no self-reg), admin user provision, JWT
- Analyze: sales kit + initial pain-fit from form/upload
- Persona generator: 15 personas (5/tier) w/ pain variety, negotiation, init mode, channel, latent/revealable, wrong_text special
- Chat simulator: per-mode initiation, one-shot, hidden signals, judge-LLM debrief+coaching
- Trainee loop: win/lose board, weak-areas, user-generated personas
- Admin analytics; EN+TH Vue SPA served by Flask
- Deploy: Dockerfile, docker-compose, README, eng-log + HANDOFF
- Tests (mock LLM): m0/m1/routes/e2e all pass
This commit is contained in:
Macky
2026-08-07 15:31:06 +07:00
commit c3d31c06e2
70 changed files with 6135 additions and 0 deletions

122
frontend/src/i18n/index.js Normal file
View File

@@ -0,0 +1,122 @@
// Minimal i18n (EN + TH) via a reactive locale.
import { reactive } from 'vue'
const messages = {
en: {
app: 'Sales Trainer',
login: 'Login',
logout: 'Logout',
email: 'Email',
password: 'Password',
loginError: 'Invalid credentials',
dashboard: 'Dashboard',
groups: 'Persona Groups',
myTraining: 'My Training',
adminTools: 'Admin Tools',
users: 'Users',
analytics: 'Analytics',
groupBuilder: 'Group Builder',
create: 'Create',
analyze: 'Analyze',
manual: 'Manual',
edit: 'Edit',
product: 'Product',
segment: 'Initial customer segment (optional)',
description: 'Additional description / scenario (optional)',
channel: 'Channel',
facebook: 'Facebook',
line: 'LINE',
language: 'Language',
thai: 'Thai',
english: 'English',
personas: 'Personas',
tierA: 'Tier A — Ready to buy',
tierB: 'Tier B — Unsure',
tierC: 'Tier C — Not interested but has pain',
selectPersona: 'Select a persona to practice',
chat: 'Chat',
start: 'Start',
send: 'Send',
finish: 'Finish & get result',
debrief: 'Result & Coaching',
won: 'Won',
lost: 'Lost',
notTried: 'Not tried',
score: 'Score',
pain: 'Pain',
why: 'Reason',
reveal: 'Revealed persona details',
generatePersona: 'Generate my persona',
weakAreas: 'My weak areas',
mySessions: 'My sessions',
openSaleTask: 'The customer did NOT message first. You must open the sale.',
sellerInitiated: 'You must open the sale (outbound)',
customerInitiated: 'The customer will message you first',
},
th: {
app: 'ตัวฝึกขาย',
login: 'เข้าสู่ระบบ',
logout: 'ออกจากระบบ',
email: 'อีเมล',
password: 'รหัสผ่าน',
loginError: 'อีเมลหรือรหัสผ่านไม่ถูกต้อง',
dashboard: 'หน้าหลัก',
groups: 'กลุ่มลูกค้า (Persona)',
myTraining: 'การฝึกของฉัน',
adminTools: 'เครื่องมือ Admin',
users: 'ผู้ใช้',
analytics: 'สถิติ',
groupBuilder: 'สร้างกลุ่มลูกค้า',
create: 'สร้าง',
analyze: 'วิเคราะห์',
manual: 'กำหนดเอง',
edit: 'แก้ไข',
product: 'สินค้า/บริการ',
segment: 'กลุ่มลูกค้าเบื้องต้น (ไม่บังคับ)',
description: 'คำอธิบาย/สถานการณ์เพิ่มเติม (ไม่บังคับ)',
channel: 'ช่องทาง',
facebook: 'Facebook',
line: 'LINE',
language: 'ภาษา',
thai: 'ไทย',
english: 'อังกฤษ',
personas: 'Persona',
tierA: 'ระดับ A — ตั้งใจซื้อ',
tierB: 'ระดับ B — ยังไม่แน่ใจ',
tierC: 'ระดับ C — ไม่สนใจแต่มี pain',
selectPersona: 'เลือก persona เพื่อฝึก',
chat: 'แชท',
start: 'เริ่ม',
send: 'ส่ง',
finish: 'สรุปผล',
debrief: 'ผลลัพธ์และคำแนะนำ',
won: 'ขายได้',
lost: 'ขายไม่ได้',
notTried: 'ยังไม่ได้ฝึก',
score: 'คะแนน',
pain: 'Pain',
why: 'เหตุผล',
reveal: 'ข้อมูล persona ที่ถูกซ่อนไว้',
generatePersona: 'สร้าง persona ของฉัน',
weakAreas: 'จุดที่ฉันแพ้บ่อย',
mySessions: 'การฝึกของฉัน',
openSaleTask: 'ลูกค้ายังไม่ได้ทักมา คุณต้องเป็นฝ่ายเปิดการขายเอง',
sellerInitiated: 'คุณต้องเปิดการขาย (เชิงรุก)',
customerInitiated: 'ลูกค้าจะทักมาเองก่อน',
},
}
export const i18n = reactive({
locale: localStorage.getItem('locale') || 'th',
t(key) {
return (messages[this.locale] && messages[this.locale][key]) || messages.en[key] || key
},
set(locale) {
this.locale = locale
localStorage.setItem('locale', locale)
},
})
export function useT() {
return (key) => i18n.t(key)
}