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

57
frontend/src/api/index.js Normal file
View File

@@ -0,0 +1,57 @@
// API client with JWT auth injection.
const TOKEN_KEY = 'st_token'
export function getToken() {
return localStorage.getItem(TOKEN_KEY)
}
export function setToken(t) {
if (t) localStorage.setItem(TOKEN_KEY, t)
else localStorage.removeItem(TOKEN_KEY)
}
async function request(method, url, body, isForm = false) {
const headers = {}
const token = getToken()
if (token) headers['Authorization'] = `Bearer ${token}`
let payload = body
if (!isForm && body !== undefined && body !== null) {
headers['Content-Type'] = 'application/json'
payload = JSON.stringify(body)
}
const res = await fetch(url, { method, headers, body: payload })
let data = null
try {
data = await res.json()
} catch (e) {
/* ignore json parse errors */
}
if (!res.ok) {
const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`
throw new Error(msg)
}
return data
}
export const api = {
login: (email, password) => request('POST', '/api/auth/login', { email, password }),
me: () => request('GET', '/api/auth/me'),
adminCreateUser: (b) => request('POST', '/api/admin/users', b),
adminListUsers: () => request('GET', '/api/admin/users'),
adminUpdateUser: (email, b) => request('PUT', `/api/admin/users/${email}`, b),
createGroup: (formData) => request('POST', '/api/groups', formData, true),
listGroups: () => request('GET', '/api/groups'),
getGroup: (id) => request('GET', `/api/groups/${id}`),
analyzeGroup: (id) => request('POST', `/api/groups/${id}/analyze`),
listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`),
getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`),
updatePersona: (gid, pid, b) => request('PUT', `/api/groups/${gid}/personas/${pid}`, b),
chatStart: (gid, pid) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/start`),
chatSend: (gid, pid, text) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/send`, { text }),
chatFinish: (gid, pid) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/finish`),
mySessions: () => request('GET', '/api/chat/sessions'),
myBoard: () => request('GET', '/api/me/board'),
weakAreas: () => request('GET', '/api/me/weak-areas'),
myPersonas: () => request('GET', '/api/me/personas'),
generatePersona: (b) => request('POST', '/api/me/personas/generate', b),
analytics: () => request('GET', '/api/analytics'),
}