interview-widget
v2.0.4
Published
Advanced React interview widget with STT, TTS, camera access, and ML-powered analysis
Maintainers
Readme
Interview Widget
A modern React interview experience widget with built‑in timer flow, STT (speech‑to‑text), and TTS (text‑to‑speech). It can be embedded via NPM or a CDN and configured through a single provider.
Features
- Video capture with camera integration
- Responsive design with mobile support
- TypeScript support with full type definitions
- CDN-ready for easy integration into any website
- Q&A structured interview flow
- Advanced Timer System with phase management
- Text-to-Speech integration for transcribing
- Speech-to-Text integration for voice answers
- Automatic phase transitions (thinking → answering → editing)
- Time validation before starting new questions
Quick Start
1) Environment Setup
The Interview Widget uses encryption for session storage integrity. Set up your encryption secret based on your environment:
Vite React
# .env
VITE_IW_SECRET=your-secure-encryption-seed-hereNext.js
# .env.local
NEXT_PUBLIC_IW_SECRET=your-secure-encryption-seed-hereVanilla JS / Custom Framework
<script>
// Define the secret before loading your widget
window.__IW_SECRET__ = "your-secure-encryption-seed-here";
</script>2) Install (React apps)
npm install interview-widgetMinimal usage:
import React from "react";
import { InterviewWidget, InterviewWidgetProvider } from "interview-widget";
import "interview-widget/style.css";
export default function App() {
return (
<InterviewWidgetProvider
config={{
api: {
baseUrl: "/api" // Your backend endpoint
authToken: getTokenFromYourAuthSystem(), // Get from secure backend
},
interview: {
stt: {
provider: "groq",
model: "whisper-large-v3-turbo",
language: "en",
},
tts: { provider: "piper" },
timers: {
thinkingDuration: 30,
answeringDuration: 120,
editingDuration: 30,
},
},
}}
>
<InterviewWidget
interviewId="your_interview_id"
title="Interview for JS developer"
brandName="your_brand_name"
className="iw-rounded-none iw-shadow-none"
onInterviewEnd={() => {
console.log("🎉🎉 Interview ended 🎉🎉");
}}
/>
</InterviewWidgetProvider>
);
}<!-- React 18 UMD -->
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
></script>
<!-- Styles + Widget UMD -->
<link
rel="stylesheet"
href="https://unpkg.com/interview-widget@latest/dist/widget.css"
/>
<script src="https://unpkg.com/interview-widget@latest/dist/widget.umd.js"></script>
<div id="interview-widget-container"></div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const { InterviewWidget, InterviewWidgetProvider } = window.InterviewWidget;
const app = React.createElement(
InterviewWidgetProvider,
{
config: {
api: { baseUrl: "/api" },
interview: {
timers: {
thinkingDuration: 30,
answeringDuration: 120,
editingDuration: 30,
},
stt: {
provider: "groq",
model: "whisper-large-v3-turbo",
language: "en",
},
tts: { provider: "piper" },
},
},
},
React.createElement(InterviewWidget, { interviewId="your_interview_id",brandName="your_brand_name", title: "Developer Interview", onInterviewEnd={() => {}} }
);
ReactDOM.render(app, document.getElementById("interview-widget-container"));
});
</script>Public API
InterviewWidget Props
| Prop | Type | Default | Description |
| ----------------------- | ------------ | ------------- | --------------------------------------------------------------------------- |
| interviewId | string | Required | Unique interview identifier used for backend API calls |
| title | string | "Interview" | Title displayed in the interview header |
| brandName | string | "Novara" | Brand name displayed in the widget header |
| onInterviewEnd | () => void | undefined | Called when the interview completes or the user exits |
| onInterviewDisqualify | () => void | undefined | Called when the interview is disqualified (e.g., due to cheating detection) |
| className | string | "" | Additional CSS classes applied to the outer widget container |
Wrap your app (or the widget) to provide configuration. See full config reference below.
<InterviewWidgetProvider config={{ api: { baseUrl: "/api" } }}>
<InterviewWidget />
</InterviewWidgetProvider>Configuration reference (Provider)
Pass this object to InterviewWidgetProvider via the config prop. Tables list all keys, types, and defaults.
Top-level
| Prop | Type | Default | Description |
| --------- | -------- | ----------------------- | ---------------------------------------------- |
| api | object | see below | Backend/API configuration |
| ui | object | see below | UI customization tokens |
| interview | object | see below | Interview behavior settings (timers, STT, TTS) |
API
| Prop | Type | Default | Description |
| ----------- | -------- | -------------------------- | ------------------------------------------------------ |
| baseUrl | string | "/api" | Base URL for backend endpoints |
| authToken | string | undefined | Optional bearer token appended as Authorization header |
| retryConfig | object | see below | Retry policy for fetch calls |
Retry Config
| Prop | Type | Default | Description |
| --------- | -------------------------- | ------------- | ------------------------------------------ |
| attempts | number | 3 | Number of retry attempts |
| backoff | "fixed" \| "exponential" | "exponential" | Backoff strategy |
| baseDelay | number (ms) | 1000 | Base delay between retries in milliseconds |
UI
| Prop | Type | Default | Description |
| ------------ | -------------- | --------- | -------------------------------------- |
| baseColor | string (hex) | "#3B82F6" | Primary brand color used by the widget |
| borderRadius | string (CSS) | "8px" | Global corner radius for components |
Interview
| Prop | Type | Default | Description |
| ---------- | -------- | -------------------------------- | ------------------------------------------ |
| timers | object | see below | Per-phase and global timing configuration |
| stt | object | see below | Speech-to-Text provider settings |
| tts | object | see below | Text-to-Speech provider settings |
| proctoring | object | see below | Proctoring settings and cheating detection |
Timers
| Prop | Type | Default | Description |
| -------------------------- | -------- | ------- | --------------------------------------------------------- |
| thinkingDuration | number | 30s | Timebox for the Thinking phase |
| answeringDuration | number | 120s | Timebox for the Answering phase |
| editingDuration | number | 30s | Timebox for the Editing phase |
| totalInterviewDuration | number | 600s | Overall interview time cap |
| minimumTimeForNextQuestion | number | 120s | Minimum time required to allow starting the next question |
STT (Speech-To-Text)
| Prop | Type | Default | Description |
| -------- | ------------------------------------ | ------------------------ | -------------------------------------- |
| provider | "groq" \| "deepgram" | "groq" | STT vendor to use |
| model | "whisper-large-v3-turbo" \| string | "whisper-large-v3-turbo" | STT model identifier |
| language | string | "en" | Language code passed to the STT engine |
TTS (Text-To-Speech)
| Prop | Type | Default | Description |
| -------- | --------- | ------- | ----------------- |
| provider | "piper" | "piper" | TTS vendor to use |
Proctoring
| Prop | Type | Default | Description |
| ------------------- | --------- | ------- | --------------------------------------------------------- |
| enabled | boolean | true | Enable/disable proctoring and cheating detection features |
| gazeAnalysisEnabled | boolean | true | Enable/disable gaze tracking and analysis |
| showControls | boolean | false | Show/hide proctoring control panel |
| showEngagementBar | boolean | true | Show/hide engagement metrics bar |
| showLandmarks | boolean | false | Show/hide facial landmark visualization |
How it works
The widget composes several parts:
- TimerService controls phases and timeboxes
- TTS speaks the question in the Reading phase
- STT records and transcribes the answer in the Answering/Transcribing phases
- A resilient API client hits your backend to fetch the next question and submit answers
Backend API Interface (Version 2)
The Interview Widget Version 2 introduces a more structured and resource-oriented API. All V2 endpoints are prefixed with /v2.
1. Get Interview Configuration
GET /v2/interviews/{interviewId}/config
Fetches the configuration and metadata for a specific interview session.
2. Generate Next Question
POST /v2/interviews/{interviewId}/next-question
Request Body
{
"interview_id": "string",
"is_interview_done": false
}Response Body
{
"success": true,
"data": {
"interview_id": "string",
"qna_id": "string",
"question": "What is your experience with React?",
"question_audio_data_base64": null,
"audio_length_in_milliseconds": 0,
"estimated_answering_duration": "00:00:30"
}
}3. Submit Answer
POST /v2/interviews/{interviewId}/submit-answer
Request Body
{
"qna_id": "string",
"answer_text": "string"
}4. Screenshot Upload (Signed URL)
POST /v2/interviews/{interviewId}/assets/upload-url
Request Body
{
"filename": "string",
"mime_type": "image/jpeg",
"asset_type": "screenshot"
}5. Confirm Asset Upload
POST /v2/interviews/assets/{assetId}/confirm
Called after successfully uploading the file to the signed URL.
Changelog
[v2.0.0] - 2025-12-22
Added
- Added
API_VERSIONSsupport (v1, v2). - New endpoint:
GET /v2/interviews/{interviewId}/configfor fetching session configuration. - New endpoint:
POST /v2/interviews/{interviewId}/submit-answerfor asynchronous answer submission. - New endpoint:
POST /v2/interviews/assets/{assetId}/confirmto confirm asset uploads. - New endpoint:
POST /v2/interviews/{interviewId}/transcribe-answerfor STT processing. - Implementation of
confirmScreenshotUploadinInterviewAPI. - Support for
asset_typein screenshot upload requests.
Changed
- Refactored all endpoints to follow a resource-oriented structure under
/v2/interviews/. - Question Generation: Moved from
/v1/questions/nextto/v2/interviews/{interviewId}/next-question. - Payload Change: Question generation payload simplified to only
interview_idandis_interview_done. - Screenshot Upload: Moved from
/v1/interview/{interviewId}/contentto/v2/interviews/{interviewId}/assets/upload-url. - Updated
InterviewAPIto use the newAPI_ENDPOINTSconfiguration.
Removed
- Removed legacy fields (
qna_id,question,answer,answer_duration) from thegenerateQuestionrequest body in V2 (as answer submission is now a separate step).
Timer phases
idle → fetching_question → reading_question → thinking → answering → transcribing → editing → submitting → completed
You’ll see a Start button, a Next Phase button during active phases, a live countdown, and a completion screen.
