feat: choose an AI recruiter persona before a simulation#175
feat: choose an AI recruiter persona before a simulation#175BhuvanArn wants to merge 16 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🚅 Deployed to the TalkUp.AI-pr-175 environment in talk-up-ai
|
|
Follow-up tracked: EpitechPromo2027/G-EIP-600-NAN-6-1-eip-tugdual.de-reviers#205 — end-of-simulation navigation (confirm early exit, auto-return to the roadmap on completion, and drop the standalone Kept out of this PR deliberately: different feature, touches shared files ( Worth noting for reviewers: the reopen-on-clear fix in this PR stays relevant even after #205 lands. |
What
Before a simulation, the user now chooses which AI recruiter will interview them. The choice drives the recruiter's displayed identity (name + role) across the avatar panel and the info box, so picking Marc Bernard visibly changes who you face.
Four personas, ranked by difficulty: Sophie Martin (easy) · Thomas Leroy (medium) · Claire Dubois (medium) · Marc Bernard (hard).
Scope — web only, by design
The AI pipeline wiring is deliberately NOT here. Sending
personaIdto the backend and resolving each persona'ssystemPromptinto the LLM prompt is a separate task for the AI developer. This PR changes no DTO, nocreateInterviewpayload, and nothing underserver/orai/. No new dependency.The roster's French
systemPromptstrings are authored now (so that follow-up is a wiring change, not a data-authoring one) and are intentionally unconsumed by the web app today.Design decisions
<Link>. One rule: on mount, if nothing is selected, ask.Skip — use Sophieall fall back to Sophie Martin — today's shipped hardcoded identity — so dismissing reproduces current behaviour exactly.Recruteuse IT,DRH,Directeur): in-fiction content like the names themselves, andRecruteuse ITis already live. Anglicising would regress shipped UI.The correctness invariant
No persona copy claims the AI interrupts, rushes, or talks over you — the speech-to-speech pipeline is strict turn-taking (VAD only trims silence; there is no barge-in), so such copy would describe behaviour the system cannot produce. Difficulty is expressed through what gets asked — question depth, follow-ups, scrutiny — never pacing. The modal says so outright: "you always get to finish your answer."
This is enforced by a regex guard test over both
descriptionandsystemPrompt, so it can't be quietly reintroduced.Accessibility
The codebase had no focus-trap primitive, so
useFocusTrapis new and shared. The gauge never carries meaning alone — the level word (EASY/MEDIUM/HARD) always renders beside it, and the bar isaria-hidden, so it survives greyscale and colour-blindness. Cards are a proper radiogroup with roving tabindex and arrow-key navigation.Two bugs caught before merge, worth knowing about
/applications/:id/simulations, because that link is rendered by the route, outside the workspace where the modal lives. Fixed with an optional back control inside the dialog, soaria-modalstays honest. The #195 guard was net strengthened — both paths to/roadmapare now covered where one was before.useStateinitializer that only runs at mount. The info box silently flipped Marc → Sophie and the next interview ran against a recruiter the user never chose. Fixed by deriving the open state; regression test verified to fail against the unfixed code.Testing
Change recruiterhides onisCallActiveand stays visible duringisAwaitingAiResponse(gating on the per-turn flag would flicker it every turn).persona-pickerandapplication-simulation-backpass. The modal was verified in a real browser in light and dark mode.tsc -ball clean.Follow-ups (not this PR)
personaId; resolvesystemPromptin both prompt builders (see note above).clearInterviewStorage()atuseInterviewSession.ts:388sits inside atryafter anawait, so a rejectedupdateInterviewskips it.clearPersona()was placed in afinallyspecifically to avoid inheriting it.🤖 Generated with Claude Code