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.
This commit is contained in:
@@ -47,6 +47,7 @@ export const api = {
|
||||
analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`),
|
||||
listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`),
|
||||
getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`),
|
||||
createPersonaVariant: (gid, pid) => request('POST', `/api/groups/${gid}/personas/${pid}/variant`),
|
||||
updatePersona: (gid, pid, b) => request('PUT', `/api/groups/${gid}/personas/${pid}`, b),
|
||||
chatStart: (gid, pid, scenario, locale) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/start`, { scenario, locale }),
|
||||
chatResume: (gid, pid) => request('GET', `/api/chat/${gid}/personas/${pid}/chat/resume`),
|
||||
|
||||
@@ -48,7 +48,12 @@
|
||||
<router-link v-if="p.my_outcome === 'not_tried'" :to="`/groups/${gid}/chat/${p.id}`" style="margin-top:auto">
|
||||
<button class="primary" style="width:100%">{{ i18n.t('chat') }}</button>
|
||||
</router-link>
|
||||
<div v-else class="muted" style="margin-top:auto;font-size:12px">✓ {{ i18n.t('trained') }} ({{ outcomeLabel(p.my_outcome) }})</div>
|
||||
<template v-else>
|
||||
<div class="muted" style="margin-top:auto;font-size:12px">✓ {{ i18n.t('trained') }} ({{ outcomeLabel(p.my_outcome) }})</div>
|
||||
<button class="soft" style="width:100%;margin-top:8px" @click="makeVariant(p)" :disabled="p._busy">
|
||||
{{ p._busy ? 'กำลังสร้าง…' : 'สร้างบุคคลต้นแบบจากต้นแบบนี้' }}
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +81,19 @@ async function load() {
|
||||
function byTier(t) { return personas.value.filter((p) => p.tier === t) }
|
||||
function tierLabel(t) { return i18n.t(t === 'A' ? 'tierA' : t === 'B' ? 'tierB' : 'tierC') }
|
||||
function outcomeLabel(o) {
|
||||
return o === 'won' ? i18n.t('won') : o === 'lost' ? i18n.t('lost') : i18n.t('notTried')
|
||||
return o === 'won' ? i18n.t('won') : o === 'lost' ? i18n.t('lost') : o === 'not_tried' ? i18n.t('notTried') : o || '-'
|
||||
}
|
||||
|
||||
async function makeVariant(p) {
|
||||
p._busy = true
|
||||
try {
|
||||
await api.createPersonaVariant(gid, p.id)
|
||||
await load() // reload so the new variant shows up in the list
|
||||
} catch (e) {
|
||||
alert(e.message)
|
||||
} finally {
|
||||
p._busy = false
|
||||
}
|
||||
}
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user