Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/arverma/Bihar-Police-Notebook/llms.txt

Use this file to discover all available pages before exploring further.

The dictation module provides in-app voice input for Bihar Police officers writing Hindi letters and FIR diaries. It is implemented as a pure state-machine engine with no DOM dependency — all UI concerns are handled separately by dictation-ui.js. The engine communicates exclusively through callbacks and is designed to survive language-pack discovery, cloud consent flows, and recognition interruptions without losing the active session.
The dictation FAB is hidden on screens ≤ 768 px via CSS and syncFabVisibility(). Mobile users should use their device keyboard’s built-in microphone instead.

Constants

LANGS

export const LANGS = Object.freeze({
  HI: 'hi-IN',
  EN: 'en-IN',
})
The two supported recognition language tags. hi-IN is Hindi (India); en-IN is English (India).

DEFAULT_LANG

export const DEFAULT_LANG = 'hi-IN'
The language used when no preference has been stored yet.

Spoken Punctuation — applyVoiceEdits(text)

export function applyVoiceEdits(text: string): string
Post-processes a final recognition transcript by replacing spoken punctuation phrases with the corresponding Unicode glyphs. Applied to every onFinal result before it is inserted into the editor.
Spoken phraseInserted character
नया पैराग्राफ\n\n (new paragraph)
नई लाइन\n (new line)
पूर्ण विराम (Hindi full stop / danda)
अल्पविराम, (comma)
प्रश्न चिह्न? (question mark)
new paragraph\n\n
new line\n
full stop
question mark?
comma,
Longer phrases are matched before shorter ones to avoid partial replacements. After substitution, whitespace around newlines and punctuation is collapsed so the inserted text looks clean.
text
string
required
Raw transcript string from SpeechRecognitionResult. Returns an empty string when falsy.

Device and Permission Probes

isDictationSupported()

export function isDictationSupported(): boolean
Returns true if SpeechRecognition or webkitSpeechRecognition is available on window. Used to gate the FAB render in dictation-ui.js.

probePackAvailability(lang)

export async function probePackAvailability(lang: string):
  Promise<'available' | 'downloadable' | 'downloading' | 'unavailable' | 'unsupported'>
Queries the Chrome on-device speech recognition API (SpeechRecognition.available()) to determine whether a language pack is installed. Tries 'dictation' quality first, then 'command', then no-quality as a fallback. Returns 'unsupported' when the static available() method does not exist (non-Chrome or older browser).
available
string
Language pack is installed and ready for on-device recognition.
downloadable
string
Pack can be installed by calling installLanguagePack.
downloading
string
Pack is currently being downloaded.
unavailable
string
Pack is not available for this language on this device.
unsupported
string
The SpeechRecognition.available() API is not present.

installLanguagePack(lang)

export async function installLanguagePack(lang: string): Promise<boolean>
Triggers download of the on-device language pack for lang. Must be called from a user gesture. Tries 'dictation' and 'command' quality tiers before attempting a generic install. Returns false if the SpeechRecognition.install() API is unavailable; returns true when the install succeeds.

queryMicPermission()

export async function queryMicPermission():
  Promise<'granted' | 'denied' | 'prompt' | 'unknown'>
Queries the Permissions API for the microphone permission without prompting the user. Falls back to 'unknown' when the Permissions API is unavailable or rejects the microphone name (as some browsers do).
The engine gates cloud-based recognition behind an explicit per-language user consent to make the privacy boundary clear. Consent is stored in localStorage via prefs.js.

hasCloudConsent(lang)

export function hasCloudConsent(lang: string): boolean
Returns true if the user has previously consented to cloud speech for the given language BCP-47 tag.

setCloudConsent(lang, bool)

export function setCloudConsent(lang: string, bool: boolean): void
Persists the consent decision. Called by continueWithCloud() after the user approves the cloud consent sheet in dictation-ui.js.

Engine — createDictationEngine(callbacks)

export function createDictationEngine(callbacks?: DictationCallbacks): Engine
Creates and returns a self-contained dictation engine instance. The engine holds all its state in closure variables — multiple independent instances can coexist.

Callbacks

callbacks.onStatus
(status: DictationStatus, detail?: object) => void
Fired on every status transition. detail may carry { lang } for needs-consent or { code } for error.
callbacks.onInterim
(text: string) => void
Fired with the partial (non-final) transcript during active recognition. Called with '' to clear the interim display.
callbacks.onFinal
(text: string) => void
Fired with the final transcript after applyVoiceEdits has been applied. dictation-ui.js passes this to main.js’s insertDictatedText.
callbacks.onMode
(onDevice: boolean) => void
Fired when the recognition mode changes between on-device (true) and cloud (false).
callbacks.onLevel
(level: number) => void
Fired on each animation frame with a normalised audio level [0, 1] derived from an AnalyserNode. Used to animate the FAB’s audio meter.
callbacks.onError
(code: string) => void
Fired for SpeechRecognitionError codes other than no-speech and aborted (which are silently ignored).
Fired when the engine transitions to needs-consent, passing the language that requires approval.

Engine Status Values

StatusMeaning
idleNo session running
listeningRecognition active, microphone open
pausedSession suspended; mic released
needs-consentOn-device pack unavailable, waiting for cloud consent
errorUnrecoverable error (e.g. mic denied)

Engine Methods

start(opts?)

async start(opts?: { preferLocal?: boolean, forceCloud?: boolean }): Promise<boolean>
Acquires the microphone, probes on-device availability (unless forceCloud is set), and starts SpeechRecognition. If on-device is unavailable and no cloud consent exists, transitions to needs-consent and returns false. Returns true on successful start.

pause()

pause(): void
Stops the current SpeechRecognition instance and releases the audio level loop. The session remains sessionActive so resume() can restart it. The microphone stream is kept open.

resume()

async resume(): Promise<void>
Re-creates a SpeechRecognition instance and resumes the audio level loop. Transitions status from paused back to listening.

stop()

stop(): void
Ends the session entirely. Stops recognition, closes the microphone stream, clears all timers, and transitions to idle.

toggle()

async toggle(): Promise<'started' | 'paused' | 'resumed' | 'needs-consent' | false>
Convenience method for the FAB tap handler. Pauses when listening, resumes when paused, starts when idle. Returns a string describing the action taken.

setLanguage(lang)

async setLanguage(lang: string): Promise<void>
Switches the recognition language in place. If the session is listening, restarts recognition immediately in the new language (with a consent check for cloud). If paused, the new language is adopted on the next resume().

continueWithCloud()

async continueWithCloud(): Promise<boolean>
Grants cloud consent for the current language via setCloudConsent, then calls start({ forceCloud: true }). Called from the consent sheet in dictation-ui.js.

requestMic()

async requestMic(): Promise<'granted' | 'denied'>
Prompts for microphone access without starting recognition. Used during the onboarding flow to request permission before the first session.

Engine Getters

MethodReturns
getStatus()Current DictationStatus string
getLang()Current language BCP-47 tag
isOnDevice()true when the current session uses on-device recognition
isSessionActive()true when the session is listening or paused

Engine Internals

Audio Level Metering

The engine creates an AudioContext and AnalyserNode from the microphone MediaStream. On each animation frame, it computes the RMS amplitude of the time-domain waveform and emits a normalised [0, 1] level via callbacks.onLevel:
analyser.getByteTimeDomainData(data);
let sum = 0;
for (let i = 0; i < data.length; i++) {
  const v = (data[i] - 128) / 128;
  sum += v * v;
}
const rms = Math.sqrt(sum / data.length);
callbacks.onLevel(Math.min(1, rms * 3));
The level loop stops automatically when the status leaves listening.

Auto-Restart on onend

Chrome’s SpeechRecognition fires onend after a few seconds of silence. The engine restarts the recogniser after a 100 ms delay:
instance.onend = () => {
  if (!sessionActive || status !== 'listening') return;
  clearTimeout(restartTimer);
  restartTimer = setTimeout(() => {
    recognition.start();
  }, 100);
};

2-Minute Idle Timeout

If the session runs for two minutes without any onresult event, stop() is called automatically:
const IDLE_MS = 2 * 60 * 1000;
idleTimer = setTimeout(() => { stop(); }, IDLE_MS);
bumpIdleWatch() resets this timer on every recognition result.

On-Device → Cloud Fallback

When the SpeechRecognition fires onerror with language-not-supported during an on-device session, the engine checks hasCloudConsent. If consent exists, it falls back to cloud recognition in place without interrupting the session. If consent is missing, it transitions to needs-consent.

dictation-ui.js

dictation-ui.js provides the FAB DOM and wires it to the engine created in main.js.

FAB rendering

Renders a floating action button with an audio-level ring. Visible only on screens wider than 768 px; syncFabVisibility() enforces this on resize.

Draggable FAB

The FAB is draggable so officers can reposition it away from content they are reading. Position is not persisted across page reloads.

Keyboard shortcut

Ctrl+Shift+D toggles the dictation session from anywhere in the editor.

Esc to end

Pressing Esc during an active session calls stop() and dismisses any interim transcript. Handled in the capture phase so it fires before other keydown handlers.

Build docs developers (and LLMs) love