Skip to content

About

An AI-powered technical interview preparation platform that generates role-specific questions, conducts voice-based interviews, and provides detailed feedback with scoring

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

πŸŽ™οΈ AI Tech Interview - Voice-Powered Interview Preparation Platform

Next.js Azure OpenAI Azure Speech TypeScript License

An AI-powered technical interview preparation platform that generates role-specific questions, conducts voice-based interviews, and provides detailed feedback with scoring.

Getting Started β€’ Features β€’ Architecture β€’ Azure Setup β€’ Contributing


πŸ“‹ Table of Contents


🎯 Overview

AI Tech Interview is a fullstack application designed to help software developers prepare for technical interviews through realistic, voice-based practice sessions. The platform leverages Azure AI services to:

  1. Generate tailored interview questions based on your target role and job description
  2. Read questions aloud using natural-sounding neural voices
  3. Record and transcribe your responses in real-time
  4. Evaluate answers and provide detailed feedback with actionable improvements

Why This Matters

Traditional interview preparation often lacks the pressure and spontaneity of real interviews. This platform bridges that gap by:

  • 🎀 Voice-first interaction - Practice speaking your answers, not just writing them
  • 🎯 Role-specific questions - Questions tailored to your exact target position and seniority level
  • πŸ“Š Objective feedback - AI-powered evaluation with consistent scoring criteria
  • ⚑ Instant results - No waiting for feedback; get insights immediately after each session

✨ Key Features

Feature Description
🎯 Seniority-Aligned Questions Questions are generated matching your target seniority level (Junior, Semi-Senior, Senior)
🎀 Voice Interaction Text-to-Speech reads questions; Speech-to-Text captures your responses
⏱️ Timed Responses Each question has a time limit (1-10 min) based on category with visible countdown
πŸ€– AI-Powered Evaluation GPT-4o-mini analyzes responses against expected competencies
πŸ“Š Detailed Scoring Multi-dimensional scoring across 6 evaluation criteria
πŸ’Ό Role Customization Supports any technical role with custom job descriptions
πŸ“ Structured Feedback Strengths, improvements, and actionable suggestions
πŸ”„ Session History Track progress across multiple practice sessions

πŸ”„ How It Works

Interview Flow

(The voice-driven interview loop)

πŸ“Έ See more visuals: Check docs/README.md for architecture diagrams, screenshots, and detailed component descriptions.

⏱️ Response Timer Flow (MVP)

stateDiagram-v2
    [*] --> QuestionDisplayed
    QuestionDisplayed --> Recording : User Clicks "Start"

    state Recording {
        [*] --> CountingDown
        CountingDown --> Timeout : Timer reaches 0
        CountingDown --> ManualStop : User clicks "Stop"
    }

    Timeout --> Processing : Auto-save
    ManualStop --> Processing : Save

    Processing --> [*] : Transcript Ready
Loading

(State diagram of the recording process)

Key Points:

  • βœ… User must press a button to start recording (gives time to think)
  • βœ… Visible countdown timer shows remaining time (1-10 min based on category)
  • βœ… Recording auto-stops when time runs out
  • βœ… User can manually stop earlier if finished
  • βœ… Whatever was spoken is saved and transcribed

πŸ› οΈ Technology Stack

Layer Technology Version Purpose
Frontend Next.js (App Router) 16.x React framework with Server Components
Runtime React 19.x UI library with concurrent features
Language TypeScript 5.x Type-safe development
Styling Tailwind CSS 4.x Utility-first CSS with @theme inline
AI/LLM Azure OpenAI GPT-4o-mini Question generation & evaluation
Text-to-Speech Azure Speech Service Neural voices Reading questions aloud
Speech-to-Text Azure Speech Service Real-time STT Transcribing user responses
Database PostgreSQL - Session and response storage

Why Next.js 16?

  • Turbopack by default - No need for --turbopack flag
  • Server Actions - Seamless server-side operations without explicit API routes
  • React 19 - Latest concurrent features and improved performance
  • App Router - Modern file-based routing with layouts and nested routes
  • Streaming - Real-time response streaming support
  • TypeScript First - Excellent type safety and developer experience

πŸ—οΈ Architecture

System Architecture

(The hybrid architecture: A local Next.js app connecting to Azure AI services)


πŸš€ Getting Started

Prerequisites

  • Node.js 20.x or higher
  • pnpm
  • Azure subscription with:
    • Azure OpenAI Service access
    • Azure Speech Service resource
  • Modern browser with microphone access (Chrome, Firefox, Edge recommended)

Installation

  1. Clone the repository
git clone https://github.com/cristofima/AI-Tech-Interview.git
cd AI-Tech-Interview
  1. Install dependencies
pnpm install
  1. Configure environment variables

Copy the example file and update with your Azure credentials:

cp .env.local.example .env.local

Then edit .env.local with your values:

# Azure OpenAI Configuration
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_DEPLOYMENT=gpt-5.4-mini
AZURE_OPENAI_MODEL=gpt-5.4-mini
AZURE_OPENAI_API_VERSION=v1

# Azure Speech Service Configuration
AZURE_SPEECH_KEY=your-speech-key
AZURE_SPEECH_REGION=eastus

πŸ’‘ Terraform Users: Run terraform output -raw env_file_content > .env.local in the infra/ folder to auto-generate this file after provisioning.

Also set the PostgreSQL connection string and apply the schema:

# .env.local
DATABASE_URL=<your-postgresql-connection-string>

pnpm exec prisma generate
pnpm exec prisma db push

Use db push only for a new development database after reviewing the target. For an existing database, review the additive response-sync SQL, then apply it with pnpm exec prisma db execute --file prisma\sql\001_offline_sync_columns.sql. Do not use --accept-data-loss for this upgrade. Prisma CLI currently loads .env, not .env.local; provide DATABASE_URL through the shell environment or the existing .env configuration without committing credentials.

  1. Run the development server
pnpm dev

Note: Next.js 16 uses Turbopack by default - no --turbopack flag needed!

  1. Open your browser

Navigate to http://localhost:3000


πŸ”· Azure Services Setup

1. Azure OpenAI Service

Create the Resource

  1. Go to Azure Portal
  2. Search for "Azure OpenAI" and click Create
  3. Select your subscription and resource group
  4. Choose a region (e.g., East US)
  5. Provide a unique name for your resource
  6. Select pricing tier (Standard S0)
  7. Review and create

Deploy a Model

  1. Navigate to your Azure OpenAI resource
  2. Go to Model deployments β†’ Manage Deployments
  3. Click Create new deployment
  4. Select model: gpt-5.4-mini, version 2026-03-17, deployment type Global Standard
  5. Provide a deployment name (e.g., gpt-5.4-mini)
  6. Set capacity within the available model quota for your active subscription

Get Credentials

  1. Go to Keys and Endpoint in your resource
  2. Copy KEY 1 or KEY 2 β†’ AZURE_OPENAI_API_KEY
  3. Copy Endpoint β†’ AZURE_OPENAI_ENDPOINT

2. Azure Speech Service

Create the Resource

  1. Go to Azure Portal
  2. Search for "Speech" and select Speech Services
  3. Click Create
  4. Select your subscription and resource group
  5. Choose a region (e.g., East US)
  6. Provide a unique name
  7. Select pricing tier (Free F0 for development, Standard S0 for production)
  8. Review and create

Get Credentials

  1. Go to Keys and Endpoint in your Speech resource
  2. Copy KEY 1 β†’ AZURE_SPEECH_KEY
  3. Note the Location/Region β†’ AZURE_SPEECH_REGION

Supported Azure OpenAI Models

Model Version Features
gpt-5.4-mini 2026-03-17 GA; Chat Completions, JSON Mode, Structured Outputs; default deployment
gpt-5.4 2026-03-05 GA; Chat Completions, JSON Mode, Structured Outputs; higher-capability alternative

Verify the model lifecycle and regional availability before provisioning. Global Standard can process requests outside the resource region.

The client defaults to the v1 GA API. AZURE_OPENAI_DEPLOYMENT is the deployment name; AZURE_OPENAI_MODEL is the underlying model name and defaults to gpt-5.4-mini. GPT-5 requests use max_completion_tokens and low reasoning effort, without temperature; reasoning consumes part of the completion budget. For an existing GPT-4 deployment with a custom name, set AZURE_OPENAI_MODEL explicitly to preserve its sampling parameters. A dated AZURE_OPENAI_API_VERSION still selects the legacy Azure client.


πŸ“ Project Structure

AI-Tech-Interview/
β”œβ”€β”€ infra/                              # Terraform Infrastructure-as-Code
β”‚   β”œβ”€β”€ main.tf                         # Azure resources (OpenAI, Speech)
β”‚   β”œβ”€β”€ variables.tf                    # Input variables
β”‚   β”œβ”€β”€ outputs.tf                      # Output values
β”‚   β”œβ”€β”€ versions.tf                     # Terraform & provider versions
β”‚   └── README.md                       # Infrastructure documentation
β”œβ”€β”€ src/                                # Next.js source code
β”‚   β”œβ”€β”€ app/                            # App Router (pages, layouts, API)
β”‚   β”‚   β”œβ”€β”€ api/                        # API Routes (sessions, responses, evaluate, speech)
β”‚   β”‚   β”œβ”€β”€ history/                    # Session history page
β”‚   β”‚   β”œβ”€β”€ interview/                  # Interview room page
β”‚   β”‚   β”œβ”€β”€ results/                    # Results dashboard page
β”‚   β”‚   β”œβ”€β”€ globals.css                 # Global styles + Tailwind theme
β”‚   β”‚   β”œβ”€β”€ layout.tsx                  # Root layout
β”‚   β”‚   └── page.tsx                    # Home page
β”‚   β”œβ”€β”€ components/                     # React Components
β”‚   β”‚   β”œβ”€β”€ InterviewRoom.tsx           # Main interview logic
β”‚   β”‚   β”œβ”€β”€ OfflineStatusIndicator.tsx  # Sync status panel (pending / retrying / needs attention)
β”‚   β”‚   β”œβ”€β”€ ResponseSyncBadge.tsx       # Per-answer sync status
β”‚   β”‚   β”œβ”€β”€ PendingUploadsNotice.tsx    # Results-page warning for unsynced answers
β”‚   β”‚   β”œβ”€β”€ PermissionsCheck.tsx        # Mic/Speech permissions
β”‚   β”‚   β”œβ”€β”€ ScoreCard.tsx               # Result visualization
β”‚   β”‚   └── SessionForm.tsx             # Setup form
β”‚   β”œβ”€β”€ generated/                      # Generated code (Prisma)
β”‚   β”œβ”€β”€ hooks/                          # Custom Hooks
β”‚   β”‚   β”œβ”€β”€ useAudioRecorder.ts         # Audio recording logic
β”‚   β”‚   β”œβ”€β”€ useOfflineSupport.ts        # React binding for the sync controller
β”‚   β”‚   β”œβ”€β”€ useSpeechRecognition.ts     # Azure STT
β”‚   β”‚   └── useSpeechSynthesis.ts       # Azure TTS
β”‚   β”œβ”€β”€ lib/                            # Libraries & Utilities
β”‚   β”‚   β”œβ”€β”€ azure-openai.ts             # OpenAI client
β”‚   β”‚   β”œβ”€β”€ azure-speech.ts             # Speech SDK
β”‚   β”‚   β”œβ”€β”€ offline-storage.ts          # IndexedDB store for recorded answers
β”‚   β”‚   β”œβ”€β”€ response-sync.ts            # Upload engine (claim β†’ upload β†’ confirm)
β”‚   β”‚   β”œβ”€β”€ sync-controller.ts          # Cross-tab coordination + scheduler
β”‚   β”‚   β”œβ”€β”€ response-submission.ts      # Server-side idempotent persistence
β”‚   β”‚   β”œβ”€β”€ prisma.ts                   # DB client
β”‚   β”‚   β”œβ”€β”€ prompts.ts                  # System prompts
β”‚   β”‚   └── utils.ts                    # Helpers
β”‚   └── types/                          # TypeScript Definitions
β”œβ”€β”€ prisma/                             # Schema + prisma/sql (manual schema changes)
β”œβ”€β”€ tests/                              # unit (mocked) and integration (PostgreSQL) tests
β”œβ”€β”€ public/                             # Static assets
β”œβ”€β”€ scripts/                            # Utility scripts (diagrams, etc.)
└── docs/                               # Documentation & Images
    β”œβ”€β”€ diagrams/                       # Architecture diagrams
    └── screenshots/                    # Application screenshots

Folder Organization Rationale

Folder Purpose
infra/ Terraform IaC for Azure resources (kept at root for clear separation)
src/ All Next.js application code (cleaner root directory)
public/ Static assets (must remain at root for Next.js)

Key Configuration Files (Next.js 16)

postcss.config.mjs - Tailwind 4

const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};
export default config;

src/app/globals.css - Tailwind 4 Theme

@import "tailwindcss";

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

tsconfig.json - Path Alias Configuration

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules", "infra"]
}

eslint.config.mjs - ESLint 9 Flat Config

import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;

🎯 Seniority-Based Question Generation

⚠️ Session Inputs

Session Setup Form

When creating an interview session, users provide the following fields:

Field Description Example Validation
Role Title The target position with seniority level "Senior FullStack .NET/Angular Developer" βœ… Required, min 10 characters
Company Name The company you're interviewing for "Google", "Microsoft", "Amazon" βšͺ Optional
Job Description Full job posting or key responsibilities "Design and implement scalable APIs..." βœ… Required, min 50 characters

Note: Role Title and Job Description are mandatory. The Company Name is optional but can help tailor questions to the company's culture and tech stack.

Important: Both fields are mandatory. The system will not generate questions without a complete role title and job description. The seniority level (Junior, Mid, Senior) is extracted from the role title to ensure questions match the expected competency level.

Question Distribution Logic

Questions are generated to match the specified seniority level:

Target Seniority Senior Questions Semi-Senior Questions Junior Questions
Junior 0% 20% 80%
Semi-Senior/Mid 20% 60% 20%
Senior 70-80% 20-30% 0%

Example: Senior FullStack .NET/Angular

For a Senior FullStack .NET/Angular Developer role, the question distribution would be:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  10 INTERVIEW QUESTIONS                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 70-80% Senior    β”‚
β”‚  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                            20-30% Semi-Seniorβ”‚
β”‚                                           0% Junior        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Question Categories & Response Time Limits

Each question category has a recommended response time based on real interview scenarios:

Category Description Examples Time Limit Rationale
Technical Deep-dive into specific technologies ".NET Core DI, Angular signals, RxJS" ⏱️ 1-4 min Direct answers with depth
System Design Architecture and scalability "Design a real-time notification system" ⏱️ 5-10 min Condensed version of real 30-45 min interviews
Behavioral Leadership, communication, problem-solving "Describe a technical decision you influenced" ⏱️ 2-5 min Full STAR method response
Problem Solving Algorithmic thinking and debugging "How would you optimize this query?" ⏱️ 2-6 min Explain approach + solution

⏱️ Response Time Constraints

Constraint Value Description
Minimum 60 seconds (1 min) Ensures meaningful, complete responses
Maximum 600 seconds (10 min) Allows thorough System Design answers
Default 240 seconds (4 min) Standard for most technical questions

Why 10 minutes max? In real interviews, System Design questions take 30-45 minutes. Since this is a practice tool where users prepare answers beforehand, 10 minutes allows for a condensed but realistic response covering: requirements clarification, high-level design, key components, and trade-offs.

How it works: When the user presses the "Start Recording" button, a visible countdown timer begins on screen. The recording automatically stops when the time limit is reached, or the user can manually stop it earlier. The system saves whatever the user managed to say within the time limit.

Question Generation Prompt

The AI uses this structured prompt to generate seniority-aligned questions:

const systemPrompt = `You are a technical interview expert specializing in software engineering roles.

CRITICAL RULES:
1. Questions MUST align with the seniority level specified in the role title
2. For Senior roles: 70-80% Senior-level questions, 20-30% Semi-Senior, 0% Junior
3. For Mid-level roles: 20% Senior, 60% Mid-level, 20% Junior  
4. For Junior roles: 0% Senior, 20% Mid-level, 80% Junior
5. Senior questions should test: architecture decisions, system design, leadership, mentoring
6. Never include junior-level questions for senior positions

Generate 10 interview questions based on the role and job description provided.

Output JSON format:
{
  "questions": [
    {
      "id": 1,
      "question": "...",
      "difficulty": "senior|mid|junior",
      "category": "technical|behavioral|system-design|problem-solving",
      "expectedTopics": ["topic1", "topic2"],
      "timeLimit": 240 // seconds (min: 60, max: 600)
    }
  ]
}

Time limit guidelines by category:
- technical: 60-240 seconds (1-4 min)
- system-design: 300-600 seconds (5-10 min)
- behavioral: 120-300 seconds (2-5 min)
- problem-solving: 120-360 seconds (2-6 min)`;

πŸ“Š Evaluation Metrics

Scoring System (100 Points Total)

Metric Weight Description
Relevance 25% How well the answer addresses the specific question asked
Technical Accuracy 25% Correctness of technical concepts, terminology, and best practices
Clarity 20% Clear, concise communication without rambling or tangents
Depth 15% Level of detail, thoroughness, and real-world examples
Structure 10% Logical organization (problem β†’ approach β†’ solution)
Confidence 5% Speech fluency, minimal filler words ("um", "uh", "like")

Score Breakdown

Performance Bands

Score Range Rating Description Hiring Signal
90-100 🌟 Excellent Exceptional responses demonstrating mastery Strong hire
75-89 βœ… Good Solid answers with minor gaps Hire with minor concerns
60-74 ⚠️ Satisfactory Adequate but needs improvement Consider with reservations
40-59 πŸ“ Needs Work Significant gaps in knowledge or communication Not ready
0-39 ❌ Poor Major deficiencies requiring substantial preparation Major concerns

Results Dashboard Results By Question

Evaluation Prompt Template

const evaluationPrompt = `You are an expert technical interviewer evaluating a candidate response.

CONTEXT:
- Role: {role}
- Seniority Expected: {seniority}
- Question: {question}
- Expected Topics: {expectedTopics}
- Candidate Response: {transcribedResponse}

EVALUATION CRITERIA:
Score each criterion from 0-100:

1. Relevance (25%): Does the answer directly address what was asked?
2. Technical Accuracy (25%): Are concepts, terminology, and best practices correct?
3. Clarity (20%): Is the answer clear, concise, and well-articulated?
4. Depth (15%): Does it show real-world experience and thorough understanding?
5. Structure (10%): Is there a logical flow (context β†’ approach β†’ outcome)?
6. Confidence (5%): Is speech fluent with minimal filler words?

SENIORITY EXPECTATIONS:
- For Senior roles: Expect architectural thinking, leadership examples, mentoring mentions
- For Mid-level roles: Expect solid technical execution and problem-solving
- For Junior roles: Expect foundational knowledge and learning attitude

Output JSON:
{
  "scores": {
    "relevance": 85,
    "technicalAccuracy": 90,
    "clarity": 75,
    "depth": 80,
    "structure": 70,
    "confidence": 85
  },
  "overallScore": 82,
  "performanceBand": "good",
  "feedback": {
    "strengths": ["Clear explanation of...", "Good use of..."],
    "improvements": ["Could elaborate more on...", "Consider mentioning..."],
    "suggestion": "To strengthen this answer, try using the STAR method..."
  }
}`;

Answer Recommendations


πŸ“‘ API Reference

Session Management

Create Session

POST /api/sessions
Content-Type: application/json

{
  "roleTitle": "Senior FullStack .NET/Angular Developer",
  "companyName": "Google",  // Optional
  "jobDescription": "We are looking for a Senior FullStack Developer..."
}

Response:

{
  "id": "sess_abc123",
  "roleTitle": "Senior FullStack .NET/Angular Developer",
  "companyName": "Google",
  "seniorityLevel": "senior",
  "createdAt": "2024-12-14T10:00:00Z",
  "status": "in-progress"
}

Retrieve Session & Questions

Get Session Details

GET /api/sessions/{id}

Response:

{
  "success": true,
  "data": {
    "session": {
      "id": "sess_abc123",
      "roleTitle": "Senior FullStack .NET/Angular Developer",
      "status": "in-progress"
    },
    "questions": [
      {
        "id": 1,
        "question": "Describe how you would architect...",
        "difficulty": "senior",
        "category": "system-design",
        "timeLimit": 600
      }
    ]
  }
}

Note: timeLimit is in seconds. System Design questions get up to 600s (10 min) to allow for comprehensive architectural explanations, while Technical questions typically get 60-240s (1-4 min).

Speech Token

Get Token for Browser SDK

GET /api/speech/token

Response:

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJS...",
  "region": "eastus",
  "expiresAt": "2024-12-14T10:10:00Z"
}

Evaluation

Evaluate Responses

POST /api/evaluate
Content-Type: application/json
{
  "sessionId": "sess_abc123",
  "responses": [
    {
      "questionId": 1,
      "transcription": "For a high-traffic e-commerce platform, I would design..."
    }
  ]
}

Response:

{
  "sessionId": "sess_abc123",
  "overallScore": 78,
  "performanceBand": "good",
  "evaluations": [
    {
      "questionId": 1,
      "scores": {
        "relevance": 85,
        "technicalAccuracy": 82,
        "clarity": 75,
        "depth": 78,
        "structure": 70,
        "confidence": 80
      },
      "overallScore": 79,
      "feedback": {
        "strengths": ["Good understanding of microservices patterns"],
        "improvements": ["Could mention specific .NET libraries"],
        "suggestion": "Consider discussing containerization with Docker/K8s"
      }
    }
  ]
}

πŸ” Offline Response Sync

Recordings use a write-ahead path, online or offline, so failed uploads retain a local copy for retry. Browser storage deletion, quota limits, or unavailable IndexedDB can still prevent durable storage; the direct-upload fallback is not persisted:

record β†’ save to IndexedDB (pending) β†’ upload β†’ server confirms answer + audio β†’ delete local copy
                                          β”‚
                                          └─ failure β†’ retry with backoff (5s Β· 3ⁿ, capped at 5 min)
Piece File Responsibility
Storage src/lib/offline-storage.ts IndexedDB ai-interview-offline v2. Atomic claim/lease/complete/fail transitions; v1 records are migrated in place on first open.
Engine src/lib/response-sync.ts Claims due records and uploads them. Audio acknowledgement must match SHA-256, or byte size when Web Crypto is unavailable; a superseded recording can also be removed.
Coordinator src/lib/sync-controller.ts One per tab. Leader election with navigator.locks, BroadcastChannel notifications, scheduler that wakes exactly when the next retry or lease expiry is due.
Server src/lib/response-submission.ts, src/app/api/responses/sync/route.ts Idempotent, order-aware upsert. Audio is stored in question_responses.audio_blob with MIME type, size and checksum.
UI useOfflineSupport, OfflineStatusIndicator, ResponseSyncBadge, PendingUploadsNotice Real per-answer status: saved on device β†’ uploading β†’ saved / retrying (countdown) / needs attention (retry Β· discard).

Policies

  • Idempotency – each recording gets a clientResponseId (UUID) when it is recorded. A retry after a lost acknowledgement returns duplicate and rewrites nothing.
  • Ordering – each recording carries recordedAt. An older recording arriving after a newer one returns superseded and never overwrites it. Rows written by older builds (no recordedAt) are compared by completedAt.
  • Replacement – a newer recording (updated) replaces the answer and audio and deletes the stale evaluation so the results page re-evaluates it.
  • Audio integrity – the client sends audioSha256; the server verifies it (400 AUDIO_CHECKSUM_MISMATCH β†’ retried) and echoes the stored checksum back. Uploads above 25 MB are rejected (413).
  • Retries – network errors, 5xx, 408 and 429 retry forever with capped backoff; other 4xx (SESSION_NOT_FOUND, QUESTION_NOT_FOUND, …) mark the record rejected and wait for the user to retry or discard. After 5 failed attempts the UI flags the answer as needing attention.
  • Recovery – a record claimed by a tab that closed or crashed mid-upload (expired lease, 60 s) is reclaimed by the next run. Atomic IndexedDB claims prevent simultaneous claims while a lease is valid; an expired lease can produce a repeated upload, which server idempotency handles.
  • Concurrency on the server – submissions for the same question are serialised with a PostgreSQL advisory lock inside one transaction.
  • Reload – the interview room merges local records for the session, so an answer saved on the device still shows as answered after a reload (the page itself still needs the network to load).

POST /api/responses/sync (multipart): questionId, sessionId, durationSeconds, transcription?, audio?, clientResponseId?, recordedAt? (unix ms), audioSha256?, audioMimeType?, recordedOffline?. Response 201 created / 200 updated | duplicate | superseded:

{
  "success": true,
  "data": {
    "outcome": "created",
    "responseId": "…",
    "syncedAt": "…",
    "audio": { "stored": true, "bytes": 48213, "sha256": "…" }
  }
}

πŸ§ͺ Testing

Command What it runs Needs
pnpm test Mocked checks – storage/migration, engine, tab coordination, submission policy, route handler (Prisma mocked), hook and components (jsdom). 100+ tests. nothing (fake IndexedDB, stubbed fetch)
pnpm test:integration Real PostgreSQL – repeat submissions, ordering, audio bytes/SHA-256 round-trip, concurrent retries, route end-to-end. Skipped with a warning when DATABASE_URL is unset. DATABASE_URL + prisma db push
pnpm test:all both
pnpm typecheck / pnpm lint / pnpm build static checks and production build prisma generate
pnpm test                                   # no services required
# Set DATABASE_URL to a dedicated test database in your shell first.
pnpm exec prisma db push
pnpm test:integration

No test talks to Azure OpenAI or Azure Speech; the integration suite creates its own session, runs, and deletes it.


πŸ’° Cost Estimation

Azure OpenAI Service (GPT-5.4-mini)

Model: gpt-5.4-mini (version 2026-03-17), Global Standard.

Use the current Azure OpenAI pricing for your region, currency, and agreement. Estimate input, cached input, output, and reasoning usage; previous GPT-4o-mini estimates do not apply.

Terraform defaults to 30 capacity units. TPM/RPM conversion and quota are model- and subscription-specific; verify them before deployment rather than assuming 30K TPM or 3,000 requests/minute.

Azure Speech Service

Default SKU: S0 (Standard) - Pay-as-you-go

Feature Free Tier (F0) Standard Tier (S0) Price
Text-to-Speech (Neural) 500K characters/month $16 / 1M characters
Text-to-Speech (Neural HD) N/A $28 / 1M characters
Speech-to-Text (Standard) 5 hours/month $1 / hour (real-time)
Speech-to-Text (Custom) N/A $1.40 / hour (real-time)

Example Usage:

  • 100 questions β‰ˆ 50K TTS characters β‰ˆ $0.80 (or free with F0)
  • 100 responses β‰ˆ 2 hours STT β‰ˆ $2.00 (or free with F0)

Estimated Total Cost

Calculate totals from measured GPT-5.4-mini token usage plus Speech consumption and state storage. A session-count-only estimate is insufficient because reasoning and generated question counts vary.

Cost Optimization Tips

  1. Use F0 (Free) tier for Speech Service during development (500K TTS chars + 5 hours STT/month)
  2. Reuse stable system prompts to benefit from model-supported prompt caching; verify the current cached-input price
  3. Use dynamic quota for batch deployments to optimize capacity usage
  4. Implement token counting and set usage limits per user/session
  5. Cache topic extraction results for similar job descriptions
  6. Monitor usage through Azure Portal > Cost Management + Billing

οΏ½ Documentation

Comprehensive documentation is available in the docs/ directory:

  • πŸ“Š Architecture Diagrams - System architecture and interview flow visualizations
  • πŸ“Έ Screenshots - UI walkthrough showing setup, interview, and results pages
  • πŸ“ Lessons Learned - Technical challenges and solutions from building the platform
  • πŸ—οΈ Infrastructure Guide - Terraform deployment guide for Azure resources

Quick Links:


οΏ½πŸ—ΊοΈ Roadmap

Phase 1: Core MVP βœ…

  • Project architecture design
  • Next.js 16 project setup with TypeScript
  • Session creation with role & job description (mandatory fields)
  • Azure OpenAI integration for question generation
  • Seniority-aligned question logic
  • Countdown timer per question (1-10 min based on category)
  • Azure TTS for reading questions
  • Azure STT for recording responses with auto-stop
  • Basic evaluation with scoring
  • Results display

Phase 2: Enhanced Experience

  • Audio waveform visualization
  • Response playback feature
  • Detailed feedback with charts
  • Export results as PDF
  • Dark/light theme support
  • Pause/resume recording

Phase 3: Advanced Features

  • Multiple interview modes (behavioral, coding, system design)
  • Practice history and progress tracking
  • Custom question banks
  • Real-time coaching during responses
  • Multi-language support
  • Team/enterprise features

🀝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Verification

pnpm test
pnpm typecheck
pnpm lint
pnpm build

pnpm test runs unit tests for IndexedDB storage, the response sync engine, the cross-tab sync controller, per-question status helpers, React sync components, server-side submission policy, and the sync route. IndexedDB and database writes are mocked; these tests do not access PostgreSQL or Azure. Controller tests use injected locks, channels, network events, and timers. React tests use jsdom and Testing Library with mocked network requests. Use pnpm test:watch during development.

vitest.config.mts also defines an integration project (pnpm test:integration) and a combined runner (pnpm test:all). PostgreSQL integration tests exercise audio persistence, repeated submissions, ordering, and concurrent retries. Set DATABASE_URL to a dedicated test database with the current schema before running them. The integration setup loads .env through dotenv/config, not .env.local; tests are skipped if no connection is configured. Tests create temporary sessions and remove their data afterward.

Offline Sync Database Setup

Before using this phase against an existing database, review and apply 001_offline_sync_columns.sql to the intended database. It adds five nullable columns and a unique index without dropping existing data. Applying this SQL does not create the rest of the application's schema. Regenerate the local Prisma client afterward:

pnpm exec prisma generate

The local response queue uses schema version 2, migrates existing recordings, leases uploads across tabs, and schedules retriable failures with exponential backoff. Terminal failures remain stored for explicit retry or discard.

SyncController adds startup recovery, automatic retry timers, network and visibility triggers, Web Locks coordination, and BroadcastChannel notifications. Without Web Locks, atomic IndexedDB claims still prevent simultaneous claims of the same recording. It exposes queue summaries and explicit retry/discard actions without including audio blobs in observable state. Call start() to activate coordination and stop() to remove listeners and timers.

useOfflineSupport now subscribes to the shared controller and starts coordination when mounted. InterviewRoom saves every answer locally before uploading, restores pending recordings into per-question state, and displays upload/retry status. If local storage fails, it attempts one direct upload; this fallback cannot persist or automatically retry the recording.

OfflineStatusIndicator exposes queue details, retry, and confirmed discard actions. PendingUploadsNotice warns when a results page has locally stored answers that the server has not confirmed. Reload results after uploads finish to include those answers. Automated checks cover the hook and status components with mocked browser dependencies, not a live microphone or Azure Speech session.

The sync endpoint persists audio bytes and their MIME type, size, and SHA-256 in PostgreSQL. It serializes submissions for the same question and session with a transaction-scoped advisory lock. clientResponseId identifies retries, recordedAt prevents older recordings from overwriting newer ones, and replacing an answer invalidates its old evaluation. The client deletes its local copy only after a confirmed acknowledgement with matching audio checksum (or size when Web Crypto is unavailable), or when the server reports that a newer answer supersedes it. The response retains responseId and syncedAt and adds outcome and audio confirmation metadata.

Development Guidelines

  • Follow TypeScript best practices
  • Use Server Components where possible
  • Write meaningful commit messages
  • Add tests for new features
  • Update documentation as needed

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ™ Acknowledgments


Built with ❀️ using Azure AI Services and Next.js 16

⬆ Back to Top

About

An AI-powered technical interview preparation platform that generates role-specific questions, conducts voice-based interviews, and provides detailed feedback with scoring

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Contributors

Languages