{"_id":"@davidmokos/react-use-transcription","_rev":"4-5e9625aa9da84a67536ddfd7888d847d","name":"@davidmokos/react-use-transcription","dist-tags":{"latest":"0.0.4"},"versions":{"0.0.1":{"name":"@davidmokos/react-use-transcription","version":"0.0.1","_id":"@davidmokos/react-use-transcription@0.0.1","maintainers":[{"name":"davidmokos","email":"davidmokos@gmail.com"}],"dist":{"shasum":"663c2454ffdea1dbdedf47df37b508fde57c4f25","tarball":"https://registry.npmjs.org/@davidmokos/react-use-transcription/-/react-use-transcription-0.0.1.tgz","fileCount":6,"integrity":"sha512-q+lDWJPXmhL190RVZaTOldyjgE5BcNbp8ZzrWcWvB5yFW9qKq76vi/0I6kO7dVYJJGgfC6K2EVXuv81SwbHkOQ==","signatures":[{"sig":"MEUCIQC87JwVoTu9L5wIOuMKnTHU3wBFSGd7EQDJsG+vFGL69wIgWFgXHFplfUHMUHrgqoZZQ+/oqZDmFFyPFMg1llwcO8A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11556},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","default":"./dist/protocol.cjs"}},"gitHead":"aa5067bb938718ccc1d6af6a55dc1f024217c112","private":false,"scripts":{"dev":"tsup --watch src/index.ts src/protocol.ts --dts --format esm,cjs","lint":"echo skip","test":"echo skip","build":"tsup src/index.ts src/protocol.ts --dts --format esm,cjs --sourcemap --clean"},"_npmUser":{"name":"davidmokos","email":"davidmokos@gmail.com"},"_npmVersion":"10.9.2","description":"A zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.","directories":{},"_nodeVersion":"23.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.6.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/react-use-transcription_0.0.1_1759236128421_0.48567526220023294","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@davidmokos/react-use-transcription","version":"0.0.2","_id":"@davidmokos/react-use-transcription@0.0.2","maintainers":[{"name":"davidmokos","email":"davidmokos@gmail.com"}],"dist":{"shasum":"b204c152be186f873c811c1d3ca00ae6f8437c8e","tarball":"https://registry.npmjs.org/@davidmokos/react-use-transcription/-/react-use-transcription-0.0.2.tgz","fileCount":16,"integrity":"sha512-bozHdr9hhpMm115zdnLmN3xfw+46D7c02eZzO90dD+U3CbBblgKYAtZRlXPG/HBlFj/xe/fPRXQNYuWpjjiEcg==","signatures":[{"sig":"MEUCIQCiNpHxcLbj3Fv96VrAZATtxheIy62C5XjNPzuIWo88CwIgRL/rRHGVC1GfwIUDHv6kHIUwK8KZxlLnB2aO8wdn4q8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94541},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","default":"./dist/protocol.cjs"}},"gitHead":"aa5067bb938718ccc1d6af6a55dc1f024217c112","private":false,"scripts":{"dev":"tsup --watch src/index.ts src/protocol.ts --dts --format esm,cjs","lint":"echo skip","test":"echo skip","build":"tsup src/index.ts src/protocol.ts --dts --format esm,cjs --sourcemap --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"davidmokos","email":"davidmokos@gmail.com"},"_npmVersion":"10.9.2","description":"A zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.","directories":{},"_nodeVersion":"23.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.6.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/react-use-transcription_0.0.2_1759250627065_0.8811106898642582","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@davidmokos/react-use-transcription","version":"0.0.3","_id":"@davidmokos/react-use-transcription@0.0.3","maintainers":[{"name":"davidmokos","email":"davidmokos@gmail.com"}],"dist":{"shasum":"8b55b95eeaef0aec72c867cd5290a5525ee113cc","tarball":"https://registry.npmjs.org/@davidmokos/react-use-transcription/-/react-use-transcription-0.0.3.tgz","fileCount":16,"integrity":"sha512-jQP+s55jf9HUqI5xK+ChLyeuRwzzzPrL4WIJMTf7YRnKv2F0aqmz4hPbA9dP98rNqaUS/QYP+0nXau1GN/mKbw==","signatures":[{"sig":"MEYCIQDZtMe5NM7DAwCeNCwnz6ACiGO35NVSMKrRjEeHBVfrgQIhAP3Jf83NqSY0NTQ6p0E1sOKnpSM1SbhL+B/aTeY1aZV1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":114807},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","default":"./dist/protocol.cjs"}},"gitHead":"aa5067bb938718ccc1d6af6a55dc1f024217c112","private":false,"scripts":{"dev":"tsup --watch src/index.ts src/protocol.ts --dts --format esm,cjs","lint":"echo skip","test":"echo skip","build":"tsup src/index.ts src/protocol.ts --dts --format esm,cjs --sourcemap --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"davidmokos","email":"davidmokos@gmail.com"},"_npmVersion":"10.9.2","description":"A zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.","directories":{},"_nodeVersion":"23.7.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.6.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/react-use-transcription_0.0.3_1759257989070_0.14483705723100138","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@davidmokos/react-use-transcription","version":"0.0.4","private":false,"type":"module","main":"dist/index.cjs","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","default":"./dist/protocol.cjs"}},"types":"dist/index.d.ts","peerDependencies":{"react":">=18","react-dom":">=18"},"scripts":{"prepublishOnly":"npm run build","build":"tsup src/index.ts src/protocol.ts --dts --format esm,cjs --sourcemap --clean","dev":"tsup --watch src/index.ts src/protocol.ts --dts --format esm,cjs","lint":"echo skip","test":"echo skip"},"devDependencies":{"tsup":"^8.0.0","typescript":"^5.6.0"},"_id":"@davidmokos/react-use-transcription@0.0.4","gitHead":"81263d0098dac85d102b65ad179b0ec14db610fc","description":"A zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.","_nodeVersion":"23.7.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-X0CJ468DHJF+tOVrEbVMIQZwpMBnDU3qVYNXgXTU1BToTZAT2CayjQlngu9QDrT44d83unQWJPiNUWvClqH8Uw==","shasum":"97aae7b2c12b932eefea825076b3e9834729a953","tarball":"https://registry.npmjs.org/@davidmokos/react-use-transcription/-/react-use-transcription-0.0.4.tgz","fileCount":16,"unpackedSize":114807,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDV0cO3GDcI70sommZ/RWCujh2d2IcPUV3aN9az+l6cdwIhAJRvWKd22ujFt6flIWZ2mtK4ipkzHc/9sTX+ccbHWCaL"}]},"_npmUser":{"name":"davidmokos","email":"davidmokos@gmail.com"},"directories":{},"maintainers":[{"name":"davidmokos","email":"davidmokos@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-use-transcription_0.0.4_1759258224401_0.2059548246400178"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-30T12:42:08.339Z","modified":"2025-09-30T18:50:24.758Z","0.0.1":"2025-09-30T12:42:08.587Z","0.0.2":"2025-09-30T16:43:47.268Z","0.0.3":"2025-09-30T18:46:29.243Z","0.0.4":"2025-09-30T18:50:24.580Z"},"description":"A zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.","maintainers":[{"name":"davidmokos","email":"davidmokos@gmail.com"}],"readme":"# @scope/react-use-transcription\n\nA zero-configuration React hook that captures microphone audio, streams it to a Cloudflare Worker, and delivers real-time partial and final transcripts powered by OpenAI Whisper.\n\n- ✅ Works over a single secure WebSocket\n- ✅ Handles microphone permissions, buffering, and clean-up for you\n- ✅ Ships with sensible fallbacks and human-friendly error messages\n- ✅ Pairable with the included Cloudflare Worker for drop-in backend transcription\n\n---\n\n## Installation\n\n```bash\n# using bun\nbun add @scope/react-use-transcription\n\n# using npm\nnpm install @scope/react-use-transcription\n\n# using pnpm\npnpm add @scope/react-use-transcription\n```\n\nThe audio worklet is bundled with the package—no extra files to copy. The hook automatically injects it via a blob URL when you call `startTranscribing`.\n\n---\n\n## Quick Start\n\n```tsx\nimport { useTranscription } from '@scope/react-use-transcription';\n\nexport function TranscriptionDemo() {\n  const {\n    transcriptionStatus,\n    isRecording,\n    isProcessing,\n    startTranscribing,\n    stopTranscribing,\n    transcription,\n    partial,\n    permissionState,\n    error,\n    levels, // 10 normalized audio levels for visualisations\n  } = useTranscription({ wsUrl: 'wss://your-worker.example.com/ws' });\n\n  const busy = transcriptionStatus === 'connecting' || isProcessing;\n\n  return (\n    <div>\n      <button\n        onClick={isRecording ? stopTranscribing : startTranscribing}\n        disabled={busy || permissionState === 'denied' || permissionState === 'unsupported'}\n      >\n        {isRecording ? 'Stop Recording' : busy ? 'Processing…' : 'Start Recording'}\n      </button>\n\n      <p>Status: {transcriptionStatus}</p>\n\n      <pre>{transcription}{partial ? `\\n${partial}` : ''}</pre>\n\n      <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end', height: 40 }}>\n        {levels.map((value, index) => (\n          <div\n            key={index}\n            style={{\n              flex: 1,\n              background: '#0af',\n              opacity: 0.6,\n              height: `${Math.max(value, 0.05) * 100}%`,\n              transition: 'height 80ms ease-out'\n            }}\n          />\n        ))}\n      </div>\n\n      {error && (\n        <p style={{ color: 'crimson' }}>\n          {error.userMessage} <small>({error.code})</small>\n        </p>\n      )}\n    </div>\n  );\n}\n```\n\nServe the page over **HTTPS or localhost**, otherwise browsers will block microphone access.\n\n---\n\n## Hook API\n\n`useTranscription(options)` accepts:\n\n| Option      | Type                 | Default | Description                                  |\n|-------------|----------------------|---------|----------------------------------------------|\n| `wsUrl`     | `string`             | —       | WebSocket endpoint exposed by the worker.    |\n| `sampleRate`| `16000 \\| 48000`     | 16000   | Desired PCM sample rate; must match backend. |\n\nIt returns:\n\n| Field            | Type                                    | Notes                                                                                 |\n|------------------|-----------------------------------------|---------------------------------------------------------------------------------------|\n| `transcriptionStatus` | `'idle' \\| 'connecting' \\| 'recording' \\| 'processing'` | High-level phase for UI state machines.                                              |\n| `status`         | `'idle' \\| 'connecting' \\| 'recording' \\| 'processing'` | Alias for `transcriptionStatus` (kept for backwards compatibility).                 |\n| `isTranscribing` | `boolean`                               | `true` while a session is active (connecting, recording, or finalising).              |\n| `isRecording`    | `boolean`                               | `true` while the microphone is open and frames are being streamed.                    |\n| `isProcessing`   | `boolean`                               | `true` after `stopTranscribing` until the backend sends its final transcript.         |\n| `startTranscribing` | `() => Promise<void>`                | Opens mic, worklet, WebSocket connection. Safe to call repeatedly.                   |\n| `stopTranscribing`  | `() => Promise<void>`                | Flushes buffers and lets the worker close the socket after the final transcript.     |\n| `transcription`  | `string`                                | Accumulated final transcripts.                                                       |\n| `partial`        | `string \\| undefined`                   | Latest interim status (\"Listening…\" or \"Processing transcription…\").               |\n| `permissionState`| `'granted' \\| 'denied' \\| …`           | Mirrors [`PermissionStatus`](https://developer.mozilla.org/docs/Web/API/PermissionStatus/state). |\n| `error`          | `TranscriptionError \\| null`            | Rich error with `type`, `code`, `message`, and `userMessage` for UI display.          |\n| `levels`         | `number[]`                              | 10-sample rolling audio intensity (0–1) for animated meters or visualisers.          |\n\n`transcriptionStatus`, `isRecording`, and `isProcessing` make it easy to tailor your UI (e.g. show a spinner while finalising or disable buttons during setup) without guessing from partial transcript strings.\n\n### Error Handling\n\nThe hook normalises browser-specific microphone errors. Display `error.userMessage` to end users and inspect `error.code` for programmatic flows (retry prompts, custom tooltips, etc.).\n\n---\n\n## Deploying the Cloudflare Worker\n\nThe worker in `apps/worker` exposes the `/ws` endpoint consumed by the hook. Deploy it to your Cloudflare account with the following steps:\n\n1. **Install dependencies** (once per repo)\n   ```bash\n   bun install\n   ```\n\n2. **Configure Wrangler**\n   ```bash\n   cd apps/worker\n   wrangler login\n   ```\n\n3. **Provide secrets**\n   Use Wrangler secrets for production _and_ `apps/worker/.dev.vars` for local development:\n   ```bash\n   wrangler secret put OPENAI_API_KEY        # required for OpenAI Whisper\n   wrangler secret put ELEVEN_API_KEY       # required when TRANSCRIBER=elevenlabs\n   wrangler secret put SILENCE_THRESHOLD    # optional (defaults to 0.012)\n   wrangler secret put ELEVEN_STT_MODEL     # optional (defaults to eleven_multilingual_v2)\n   ```\n   In the Cloudflare dashboard: Workers → your worker → **Settings → Variables** → **Add variable** → choose **Secret** and enter the same keys.\n\n   For local development create `apps/worker/.dev.vars` with matching entries:\n   ```env\n   OPENAI_API_KEY=sk-...\n   ELEVEN_API_KEY=sk-...\n   SILENCE_THRESHOLD=0.01\n   ```\n   Wrangler automatically loads `.dev.vars` when you run `bun run dev` inside `apps/worker`.\n\n4. **Deploy**\n   ```bash\n   bun run deploy\n   ```\n   That script invokes `wrangler deploy` with the bundled worker.\n\n5. **Verify locally (optional)**\n   ```bash\n   bun run dev\n   ```\n   Wrangler will expose the worker on `http://localhost:8787/ws`, perfect for local testing with the example app.\n\nOnce deployed, grab the live WebSocket URL from the Wrangler output (something like `wss://asr-ws.your-account.workers.dev/ws`) and feed it to the hook's `wsUrl` option.\n\n### Silence Trimming\n\nThe worker removes frames that fall below a configurable energy threshold before batching audio for Whisper. Tune it via the `SILENCE_THRESHOLD` secret:\n\n```bash\nwrangler secret put SILENCE_THRESHOLD  # e.g. 0.01 keeps quiet speech, 0 disables trimming\n```\n\n- Default: `0.012` (≈1.2 % of full-scale amplitude).\n- Set to `0` or `false` to disable trimming entirely.\n\n---\n\n## Serving the Audio Worklet\n\nNothing extra to host—the package registers the audio worklet dynamically and streams PCM16 frames straight to the worker.\n\n---\n\n## Transcription Providers\n\nSet the `TRANSCRIBER` variable (defaults to `openai`) in `wrangler.toml` or as a plain text variable in the Cloudflare dashboard to switch between providers. When you specify a provider, the corresponding API key must be present—otherwise the worker throws a configuration error. If you leave it blank, the worker prefers OpenAI when that key is available, otherwise it uses ElevenLabs.\n\n| Provider value   | Requirements                         | Notes                                              |\n|------------------|---------------------------------------|----------------------------------------------------|\n| `openai` (default)| `OPENAI_API_KEY` secret               | Uses Whisper (`model=whisper-1`).                  |\n| `elevenlabs`     | `ELEVEN_API_KEY` secret               | Uses ElevenLabs STT (`model_id=eleven_multilingual_v2` by default). |\n\nOptional secrets:\n- `DEFAULT_LANG` – hint language for both providers.\n- `ELEVEN_STT_MODEL` – override the ElevenLabs model ID if needed.\n\n---\n\n## Development Tips\n\n- Use `bun run build` at the repo root to rebuild all packages, including this hook.\n- The example app in `examples/textarea-basic` demonstrates a minimal integration and makes a great starting point for UI experiments.\n- If you extend the protocol, update both this hook and the worker to keep the frame schema aligned.\n\n---\n\n## License\n\nMIT © Scope\n","readmeFilename":"README.md"}