Summary
Client half of the built-in Mock Interview feature (backend: PowerInterviewAI/backend#60).
The AI plays the interviewer: it generates questions from the candidate's CV/job context,
speaks them via the backend's Deepgram TTS proxy, listens over the existing ASR pipeline,
adaptively decides follow-ups, and produces a scored report at the end.
Scope
- State machine (
src/main/services/mock-interview.service.ts): Idle → Starting →
Generating → Speaking/Listening → Evaluating → Scoring → Finished, with a generation-token
guard against overlapping async calls and the terminal-state invariant use-assistant-service.ts
documents for RunningState - whatever fails, the state always lands on Idle or Finished.
- The transmit gate (
src/renderer/services/mock-tts.service.ts): mutes the candidate's
microphone track while the interviewer's TTS plays, so the ASR never transcribes the AI's own
question as something the candidate said. Four independent layers guard against a stranded
mute: a finally, a watchdog, a generation token, and a state-driven belt.
- Mic-only capture (
src/renderer/services/mock-transcription.service.ts): a sibling to
LiveTranscriptionService, not a mode flag added to it, so the live path's source-level-tested
race guards stay untouched. Never opens loopback capture, never touches the live transcript.
- Guards: mutual exclusion with the live assistant, an explicit (not just emergent) block on
action suggestions during a mock session, the close guard widened to cover mock content, and
Ctrl+Shift+Q routed to end a mock session when one is running.
- Setup/session/report screens under
/mock-interview, deliberately the opposite visual
contract from the live control bar - a centred column the user looks straight at, not a compact
overlay for peripheral reading.
- Export: a mock-interview report builder, wired into the existing save-history dialog by
subject (live vs. mock), with all 28 languages' export labels filled in.
- A product-wide change while in the area: the headphone notice no longer has "don't show
again" - whether the call is on speakers is a property of the machine and the meeting, not a
setting, and a permanent silence option contradicted the app's own stated reasoning for the
notice being opt-out in the first place.
Test plan
Summary
Client half of the built-in Mock Interview feature (backend: PowerInterviewAI/backend#60).
The AI plays the interviewer: it generates questions from the candidate's CV/job context,
speaks them via the backend's Deepgram TTS proxy, listens over the existing ASR pipeline,
adaptively decides follow-ups, and produces a scored report at the end.
Scope
src/main/services/mock-interview.service.ts): Idle → Starting →Generating → Speaking/Listening → Evaluating → Scoring → Finished, with a generation-token
guard against overlapping async calls and the terminal-state invariant
use-assistant-service.tsdocuments for
RunningState- whatever fails, the state always lands onIdleorFinished.src/renderer/services/mock-tts.service.ts): mutes the candidate'smicrophone track while the interviewer's TTS plays, so the ASR never transcribes the AI's own
question as something the candidate said. Four independent layers guard against a stranded
mute: a
finally, a watchdog, a generation token, and a state-driven belt.src/renderer/services/mock-transcription.service.ts): a sibling toLiveTranscriptionService, not a mode flag added to it, so the live path's source-level-testedrace guards stay untouched. Never opens loopback capture, never touches the live transcript.
action suggestions during a mock session, the close guard widened to cover mock content, and
Ctrl+Shift+Qrouted to end a mock session when one is running./mock-interview, deliberately the opposite visualcontract from the live control bar - a centred column the user looks straight at, not a compact
overlay for peripheral reading.
subject (live vs. mock), with all 28 languages' export labels filled in.
again" - whether the call is on speakers is a property of the machine and the meeting, not a
setting, and a permanent silence option contradicted the app's own stated reasoning for the
notice being opt-out in the first place.
Test plan
pnpm lint- cleanpnpm build(tsc + vite) - cleanpnpm electron:build-main- cleanpnpm test:main- all checks pass, including 6 new test files covering the state machine,the transmit gate (source-level), mic/loopback isolation (source-level), the action-suggestion
block, sentence chunking, and export label completeness
transcript (not run in this environment - no audio hardware / real Deepgram key available)