use-voice-control 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://i.imgur.com/ypVzqbg.png" width="200" />
2
+ <img src="https://i.imgur.com/ypVzqbg.png" />
3
3
  <br />
4
4
  <a href="https://www.npmjs.com/package/use-voice-control"><img src="https://img.shields.io/npm/dm/use-voice-control.svg" alt="NPM Monthly Downloads"></a>
5
5
  <a href="https://www.npmjs.com/package/use-voice-control"><img src="https://img.shields.io/npm/v/use-voice-control.svg" alt="npm version"></a>
@@ -0,0 +1,5 @@
1
+ import { TTSResult } from '../types/types';
2
+ /**
3
+ * Generate speech from text using Deepgram Aura via Cloudflare Workers AI
4
+ */
5
+ export declare function generateDeepgramSpeech(text: string, speaker?: string): Promise<TTSResult>;
@@ -0,0 +1,5 @@
1
+ import { TTSResult } from '../types/types';
2
+ /**
3
+ * Generate speech from text using Kokoro
4
+ */
5
+ export declare function generateKokoroSpeech(text: string, voice?: string): Promise<TTSResult>;
@@ -0,0 +1,25 @@
1
+ import { TTSOptions, TTSResult } from './types/types';
2
+ export * from './types/types';
3
+ /**
4
+ * Generate speech from text using the specified provider
5
+ *
6
+ * @param options - TTS configuration
7
+ * @returns Audio buffer and content type
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // Use Kokoro (default)
12
+ * const audio = await generateSpeech({
13
+ * text: "Hello world",
14
+ * voice: "af_heart"
15
+ * });
16
+ *
17
+ * // Use Deepgram
18
+ * const audio = await generateSpeech({
19
+ * text: "Hello world",
20
+ * provider: "deepgram",
21
+ * voice: "angus"
22
+ * });
23
+ * ```
24
+ */
25
+ export declare function generateSpeech(options: TTSOptions): Promise<TTSResult>;
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ const c = [
2
+ "af_heart",
3
+ "af_alloy",
4
+ "af_aoede",
5
+ "af_bella",
6
+ "af_jessica",
7
+ "af_nicole",
8
+ "af_river",
9
+ "af_sarah",
10
+ "af_sky",
11
+ "am_adam",
12
+ "am_echo",
13
+ "am_fable",
14
+ "am_fenrir",
15
+ "am_liam",
16
+ "am_michael",
17
+ "am_onyx"
18
+ ], u = [
19
+ "angus",
20
+ "asteria",
21
+ "arcas",
22
+ "orion",
23
+ "orpheus",
24
+ "athena",
25
+ "luna",
26
+ "zeus",
27
+ "perseus",
28
+ "helios",
29
+ "hera",
30
+ "stella"
31
+ ];
32
+ let t = null, n = null;
33
+ async function f() {
34
+ return t || (n ? (await n, t) : (n = (async () => {
35
+ try {
36
+ const e = await import("@huggingface/transformers"), { StyleTextToSpeech2Model: o, AutoTokenizer: a } = e, s = "hexgrad/Kokoro-82M", [i, l] = await Promise.all([
37
+ o.from_pretrained(s, {
38
+ device: "cpu",
39
+ dtype: "q8"
40
+ }),
41
+ a.from_pretrained(s)
42
+ ]);
43
+ t = { model: i, tokenizer: l }, console.log("[Kokoro] Model loaded successfully");
44
+ } catch (r) {
45
+ throw console.error("[Kokoro] Failed to load model:", r), r;
46
+ } finally {
47
+ n = null;
48
+ }
49
+ })(), await n, t));
50
+ }
51
+ async function d(r, e = "af_heart") {
52
+ c.includes(e), await f();
53
+ try {
54
+ throw new Error("Server-side Kokoro TTS requires additional setup. Use the browser-based implementation via Web Workers.");
55
+ } catch (o) {
56
+ throw console.error("[Kokoro] Error generating speech:", o), o;
57
+ }
58
+ }
59
+ function h() {
60
+ var e, o;
61
+ const r = globalThis;
62
+ try {
63
+ if (typeof r.getCloudflareContext == "function") {
64
+ const a = r.getCloudflareContext();
65
+ if ((e = a == null ? void 0 : a.env) != null && e.AI) return a.env.AI;
66
+ }
67
+ } catch {
68
+ }
69
+ return ((o = r.__env__) == null ? void 0 : o.AI) ?? r.AI;
70
+ }
71
+ async function m(r, e = "angus") {
72
+ const o = u.includes(e) ? e : "angus", a = h();
73
+ if (!a)
74
+ throw new Error("Cloudflare AI binding not available");
75
+ return {
76
+ audio: await a.run("@cf/deepgram/aura-1", {
77
+ text: r.slice(0, 5e3),
78
+ speaker: o,
79
+ encoding: "mp3"
80
+ }),
81
+ contentType: "audio/mpeg"
82
+ };
83
+ }
84
+ async function _(r) {
85
+ const { text: e, provider: o = "kokoro", voice: a = "af_heart" } = r;
86
+ if (!e || typeof e != "string" || e.trim().length === 0)
87
+ throw new Error("Text is required");
88
+ switch (o) {
89
+ case "kokoro":
90
+ return d(e, a);
91
+ case "deepgram":
92
+ return m(e, a);
93
+ default:
94
+ throw new Error(`Unknown TTS provider: ${o}`);
95
+ }
96
+ }
97
+ export {
98
+ u as DEEPGRAM_SPEAKERS,
99
+ c as KOKORO_VOICES,
100
+ _ as generateSpeech
101
+ };
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../speech/types/types.ts","../speech/core/kokoro.ts","../speech/core/deepgram.ts","../speech/index.ts"],"sourcesContent":["/**\n * @fileoverview Type definitions for text-to-speech providers\n */\n\nexport type TTSProvider = \"kokoro\" | \"deepgram\";\n\nexport interface TTSOptions {\n text: string;\n provider?: TTSProvider;\n voice?: string;\n}\n\nexport interface TTSResult {\n audio: ArrayBuffer;\n contentType: string;\n}\n\n// Kokoro voices from the model\nexport const KOKORO_VOICES = [\n \"af_heart\", \"af_alloy\", \"af_aoede\", \"af_bella\",\n \"af_jessica\", \"af_nicole\", \"af_river\", \"af_sarah\", \"af_sky\",\n \"am_adam\", \"am_echo\", \"am_fable\", \"am_fenrir\",\n \"am_liam\", \"am_michael\", \"am_onyx\"\n] as const;\n\nexport type KokoroVoice = (typeof KOKORO_VOICES)[number];\n\n// Deepgram Aura speakers\nexport const DEEPGRAM_SPEAKERS = [\n \"angus\", \"asteria\", \"arcas\", \"orion\", \"orpheus\", \"athena\",\n \"luna\", \"zeus\", \"perseus\", \"helios\", \"hera\", \"stella\",\n] as const;\n\nexport type DeepgramSpeaker = (typeof DEEPGRAM_SPEAKERS)[number];\n","/**\n * @fileoverview Kokoro TTS provider implementation using Hugging Face transformers\n * Runs on Node.js CPU via transformers library\n */\nimport type { TTSResult } from \"../types/types\";\nimport { KOKORO_VOICES, type KokoroVoice } from \"../types/types\";\n\nlet ttsInstance: any = null;\nlet modelLoading: Promise<any> | null = null;\n\n/**\n * Lazy-load Kokoro model using Hugging Face transformers (happens once per server instance)\n */\nasync function getKokoroTTS() {\n if (ttsInstance) return ttsInstance;\n\n if (modelLoading) {\n await modelLoading;\n return ttsInstance;\n }\n\n modelLoading = (async () => {\n try {\n // Dynamic import to load transformers library. The specifier is kept in\n // a variable so the type-checker/bundler treats it as an optional runtime\n // dependency (see optionalDependencies) rather than a build-time one.\n const transformersModule = \"@huggingface/transformers\";\n const transformers: any = await import(/* @vite-ignore */ transformersModule);\n const { StyleTextToSpeech2Model, AutoTokenizer } = transformers;\n\n const model_id = \"hexgrad/Kokoro-82M\";\n\n // Load model and tokenizer in parallel\n const [model, tokenizer] = await Promise.all([\n StyleTextToSpeech2Model.from_pretrained(model_id, {\n device: \"cpu\",\n dtype: \"q8\"\n }),\n AutoTokenizer.from_pretrained(model_id)\n ]);\n\n ttsInstance = { model, tokenizer };\n\n console.log(\"[Kokoro] Model loaded successfully\");\n } catch (error) {\n console.error(\"[Kokoro] Failed to load model:\", error);\n throw error;\n } finally {\n modelLoading = null;\n }\n })();\n\n await modelLoading;\n return ttsInstance;\n}\n\n/**\n * Generate speech from text using Kokoro\n */\nexport async function generateKokoroSpeech(\n text: string,\n voice: string = \"af_heart\"\n): Promise<TTSResult> {\n // Validate voice\n const kokoroVoice = KOKORO_VOICES.includes(voice as KokoroVoice)\n ? (voice as KokoroVoice)\n : \"af_heart\";\n\n const tts = await getKokoroTTS();\n\n try {\n // For Node.js server-side TTS, we'd need the complete transformers implementation\n // This is a placeholder showing the expected interface\n // Consider using the browser-based implementation via Web Workers for production\n\n throw new Error(\"Server-side Kokoro TTS requires additional setup. Use the browser-based implementation via Web Workers.\");\n } catch (error) {\n console.error(\"[Kokoro] Error generating speech:\", error);\n throw error;\n }\n}\n","/**\n * @fileoverview Deepgram TTS provider implementation using Cloudflare Workers AI\n * Requires Cloudflare AI binding\n */\nimport type { TTSResult } from \"../types/types\";\nimport { DEEPGRAM_SPEAKERS, type DeepgramSpeaker } from \"../types/types\";\n\n/**\n * Resolve the Cloudflare Workers AI binding at runtime without a hard\n * dependency on the host application. Consumers running on Cloudflare can\n * expose the context by setting `globalThis.getCloudflareContext` (as the\n * `@opennextjs/cloudflare` / `@cloudflare/next-on-pages` helpers do) or by\n * placing the bound `env` on `globalThis.__env__`.\n */\nfunction resolveCloudflareAI(): any {\n const g = globalThis as any;\n try {\n if (typeof g.getCloudflareContext === \"function\") {\n const ctx = g.getCloudflareContext();\n if (ctx?.env?.AI) return ctx.env.AI;\n }\n } catch {\n // CF context helper threw (e.g. called outside a request scope)\n }\n return g.__env__?.AI ?? g.AI;\n}\n\n/**\n * Generate speech from text using Deepgram Aura via Cloudflare Workers AI\n */\nexport async function generateDeepgramSpeech(\n text: string,\n speaker: string = \"angus\"\n): Promise<TTSResult> {\n // Validate speaker\n const auraVoice = DEEPGRAM_SPEAKERS.includes(speaker as DeepgramSpeaker)\n ? (speaker as DeepgramSpeaker)\n : \"angus\";\n\n const ai = resolveCloudflareAI();\n\n if (!ai) {\n throw new Error(\"Cloudflare AI binding not available\");\n }\n\n const result = await ai.run(\"@cf/deepgram/aura-1\", {\n text: text.slice(0, 5000),\n speaker: auraVoice,\n encoding: \"mp3\",\n });\n\n return {\n audio: result,\n contentType: \"audio/mpeg\",\n };\n}\n","/**\n * @fileoverview Unified text-to-speech API supporting Kokoro (default) and Deepgram\n *\n * Kokoro: Faster, more natural, runs on Node CPU\n * Deepgram: Requires Cloudflare AI binding, MP3 output\n */\nimport type { TTSOptions, TTSResult } from \"./types/types\";\nimport { generateKokoroSpeech } from \"./core/kokoro\";\nimport { generateDeepgramSpeech } from \"./core/deepgram\";\n\nexport * from \"./types/types\";\n\n/**\n * Generate speech from text using the specified provider\n *\n * @param options - TTS configuration\n * @returns Audio buffer and content type\n *\n * @example\n * ```ts\n * // Use Kokoro (default)\n * const audio = await generateSpeech({\n * text: \"Hello world\",\n * voice: \"af_heart\"\n * });\n *\n * // Use Deepgram\n * const audio = await generateSpeech({\n * text: \"Hello world\",\n * provider: \"deepgram\",\n * voice: \"angus\"\n * });\n * ```\n */\nexport async function generateSpeech(\n options: TTSOptions\n): Promise<TTSResult> {\n const { text, provider = \"kokoro\", voice = \"af_heart\" } = options;\n\n if (!text || typeof text !== \"string\" || text.trim().length === 0) {\n throw new Error(\"Text is required\");\n }\n\n switch (provider) {\n case \"kokoro\":\n return generateKokoroSpeech(text, voice);\n\n case \"deepgram\":\n return generateDeepgramSpeech(text, voice);\n\n default:\n throw new Error(`Unknown TTS provider: ${provider}`);\n }\n}\n"],"names":["KOKORO_VOICES","DEEPGRAM_SPEAKERS","ttsInstance","modelLoading","getKokoroTTS","transformers","StyleTextToSpeech2Model","AutoTokenizer","model_id","model","tokenizer","error","generateKokoroSpeech","text","voice","resolveCloudflareAI","_a","_b","g","ctx","generateDeepgramSpeech","speaker","auraVoice","ai","generateSpeech","options","provider"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @fileoverview Type definitions for text-to-speech providers
3
+ */
4
+ export type TTSProvider = "kokoro" | "deepgram";
5
+ export interface TTSOptions {
6
+ text: string;
7
+ provider?: TTSProvider;
8
+ voice?: string;
9
+ }
10
+ export interface TTSResult {
11
+ audio: ArrayBuffer;
12
+ contentType: string;
13
+ }
14
+ export declare const KOKORO_VOICES: readonly ["af_heart", "af_alloy", "af_aoede", "af_bella", "af_jessica", "af_nicole", "af_river", "af_sarah", "af_sky", "am_adam", "am_echo", "am_fable", "am_fenrir", "am_liam", "am_michael", "am_onyx"];
15
+ export type KokoroVoice = (typeof KOKORO_VOICES)[number];
16
+ export declare const DEEPGRAM_SPEAKERS: readonly ["angus", "asteria", "arcas", "orion", "orpheus", "athena", "luna", "zeus", "perseus", "helios", "hera", "stella"];
17
+ export type DeepgramSpeaker = (typeof DEEPGRAM_SPEAKERS)[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-voice-control",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "React voice control with speech transcription, vocalization, and interruption (STT/TTS/VAD) support.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,19 +14,12 @@
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
16
16
  "import": "./dist/index.js"
17
- },
18
- "./hooks": {
19
- "types": "./dist/hooks/index.d.ts",
20
- "import": "./dist/hooks/index.js"
21
- },
22
- "./components": {
23
- "types": "./dist/components/index.d.ts",
24
- "import": "./dist/components/index.js"
25
17
  }
26
18
  },
27
19
  "files": [
28
20
  "dist",
29
- "src"
21
+ "speech",
22
+ "README.md"
30
23
  ],
31
24
  "scripts": {
32
25
  "build": "tsc && vite build",
@@ -34,6 +27,7 @@
34
27
  "type-check": "tsc --noEmit"
35
28
  },
36
29
  "dependencies": {
30
+ "@huggingface/transformers": "^3.8.1",
37
31
  "lucide-react": "^0.344.0",
38
32
  "@moonshine-ai/moonshine-js": "^0.1.29",
39
33
  "react": "^18.0.0",
@@ -50,5 +44,8 @@
50
44
  "react": "^18.0.0",
51
45
  "react-dom": "^18.0.0"
52
46
  },
47
+ "optionalDependencies": {
48
+ "@huggingface/transformers": "^3.0.0"
49
+ },
53
50
  "license": "rights.institute/PROSPER"
54
51
  }
@@ -0,0 +1,91 @@
1
+ import { StyleTextToSpeech2Model, AutoTokenizer, Tensor, RawAudio } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.5.1/dist/transformers.min.js";
2
+
3
+ import { phonemize } from "./phonemize.js";
4
+ import { getVoiceData, VOICES } from "./voices.js";
5
+
6
+ const STYLE_DIM = 256;
7
+ const SAMPLE_RATE = 24000;
8
+
9
+ export class KokoroTTS {
10
+ /**
11
+ * Create a new KokoroTTS instance.
12
+ * @param {import('@huggingface/transformers').StyleTextToSpeech2Model} model The model
13
+ * @param {import('@huggingface/transformers').PreTrainedTokenizer} tokenizer The tokenizer
14
+ */
15
+ constructor(model, tokenizer) {
16
+ this.model = model;
17
+ this.tokenizer = tokenizer;
18
+ }
19
+
20
+ /**
21
+ * Load a KokoroTTS model from the Hugging Face Hub.
22
+ * @param {string} model_id The model id
23
+ * @param {Object} options Additional options
24
+ * @param {"fp32"|"fp16"|"q8"|"q4"|"q4f16"} [options.dtype="fp32"] The data type to use.
25
+ * @param {"wasm"|"webgpu"|"cpu"|null} [options.device=null] The device to run the model on.
26
+ * @param {import("@huggingface/transformers").ProgressCallback} [options.progress_callback=null] A callback function that is called with progress information.
27
+ * @returns {Promise<KokoroTTS>} The loaded model
28
+ */
29
+ static async from_pretrained(model_id, { dtype = "fp32", device = null, progress_callback = null } = {}) {
30
+ const model = StyleTextToSpeech2Model.from_pretrained(model_id, { progress_callback, dtype, device });
31
+ const tokenizer = AutoTokenizer.from_pretrained(model_id, { progress_callback });
32
+
33
+ const info = await Promise.all([model, tokenizer]);
34
+ return new KokoroTTS(...info);
35
+ }
36
+
37
+ get voices() {
38
+ return VOICES;
39
+ }
40
+
41
+ list_voices() {
42
+ console.table(VOICES);
43
+ }
44
+
45
+ /**
46
+ * Generate audio from text.
47
+ *
48
+ * Note: The model will be loaded on the first call, and subsequent calls will use the same model.
49
+ * @param {string} text The input text
50
+ * @param {Object} options Additional options
51
+ * @param {keyof typeof VOICES} [options.voice="af"] The voice style to use
52
+ * @param {number} [options.speed=1] The speaking speed
53
+ * @returns {Promise<RawAudio>} The generated audio
54
+ */
55
+ async generate(text, { voice = "af", speed = 1 } = {}) {
56
+ if (!VOICES.hasOwnProperty(voice)) {
57
+ console.error(`Voice "${voice}" not found. Available voices:`);
58
+ console.table(VOICES);
59
+ throw new Error(`Voice "${voice}" not found. Should be one of: ${Object.keys(VOICES).join(", ")}.`);
60
+ }
61
+
62
+ const language = voice.at(0); // "a" or "b"
63
+ const phonemes = await phonemize(text, language);
64
+ const { input_ids } = this.tokenizer(phonemes, {
65
+ truncation: true,
66
+ });
67
+
68
+ // Select voice style based on number of input tokens
69
+ const num_tokens = Math.max(
70
+ input_ids.dims.at(-1) - 2, // Without padding;
71
+ 0,
72
+ );
73
+
74
+ // Load voice style
75
+ const data = await getVoiceData(voice);
76
+ const offset = num_tokens * STYLE_DIM;
77
+ const voiceData = data.slice(offset, offset + STYLE_DIM);
78
+
79
+ // Prepare model inputs
80
+ const inputs = {
81
+ input_ids,
82
+ style: new Tensor("float32", voiceData, [1, STYLE_DIM]),
83
+ speed: new Tensor("float32", [speed], [1]),
84
+ };
85
+
86
+ // Generate audio
87
+ const { waveform } = await this.model(inputs);
88
+
89
+ return new RawAudio(waveform.data, SAMPLE_RATE);
90
+ }
91
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Deepgram TTS provider implementation using Cloudflare Workers AI
3
+ * Requires Cloudflare AI binding
4
+ */
5
+ import type { TTSResult } from "../types/types";
6
+ import { DEEPGRAM_SPEAKERS, type DeepgramSpeaker } from "../types/types";
7
+
8
+ /**
9
+ * Resolve the Cloudflare Workers AI binding at runtime without a hard
10
+ * dependency on the host application. Consumers running on Cloudflare can
11
+ * expose the context by setting `globalThis.getCloudflareContext` (as the
12
+ * `@opennextjs/cloudflare` / `@cloudflare/next-on-pages` helpers do) or by
13
+ * placing the bound `env` on `globalThis.__env__`.
14
+ */
15
+ function resolveCloudflareAI(): any {
16
+ const g = globalThis as any;
17
+ try {
18
+ if (typeof g.getCloudflareContext === "function") {
19
+ const ctx = g.getCloudflareContext();
20
+ if (ctx?.env?.AI) return ctx.env.AI;
21
+ }
22
+ } catch {
23
+ // CF context helper threw (e.g. called outside a request scope)
24
+ }
25
+ return g.__env__?.AI ?? g.AI;
26
+ }
27
+
28
+ /**
29
+ * Generate speech from text using Deepgram Aura via Cloudflare Workers AI
30
+ */
31
+ export async function generateDeepgramSpeech(
32
+ text: string,
33
+ speaker: string = "angus"
34
+ ): Promise<TTSResult> {
35
+ // Validate speaker
36
+ const auraVoice = DEEPGRAM_SPEAKERS.includes(speaker as DeepgramSpeaker)
37
+ ? (speaker as DeepgramSpeaker)
38
+ : "angus";
39
+
40
+ const ai = resolveCloudflareAI();
41
+
42
+ if (!ai) {
43
+ throw new Error("Cloudflare AI binding not available");
44
+ }
45
+
46
+ const result = await ai.run("@cf/deepgram/aura-1", {
47
+ text: text.slice(0, 5000),
48
+ speaker: auraVoice,
49
+ encoding: "mp3",
50
+ });
51
+
52
+ return {
53
+ audio: result,
54
+ contentType: "audio/mpeg",
55
+ };
56
+ }
@@ -0,0 +1,93 @@
1
+ import { StyleTextToSpeech2Model, AutoTokenizer, Tensor, RawAudio } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.5.1/dist/transformers.min.js";
2
+ //import { StyleTextToSpeech2Model, AutoTokenizer, Tensor, RawAudio } from "@huggingface/transformers";
3
+
4
+
5
+ import { phonemize } from "./phonemize.js";
6
+ import { getVoiceData, VOICES } from "./voices.js";
7
+
8
+ const STYLE_DIM = 256;
9
+ const SAMPLE_RATE = 24000;
10
+
11
+ export class KokoroTTS {
12
+ /**
13
+ * Create a new KokoroTTS instance.
14
+ * @param {import('@huggingface/transformers').StyleTextToSpeech2Model} model The model
15
+ * @param {import('@huggingface/transformers').PreTrainedTokenizer} tokenizer The tokenizer
16
+ */
17
+ constructor(model, tokenizer) {
18
+ this.model = model;
19
+ this.tokenizer = tokenizer;
20
+ }
21
+
22
+ /**
23
+ * Load a KokoroTTS model from the Hugging Face Hub.
24
+ * @param {string} model_id The model id
25
+ * @param {Object} options Additional options
26
+ * @param {"fp32"|"fp16"|"q8"|"q4"|"q4f16"} [options.dtype="fp32"] The data type to use.
27
+ * @param {"wasm"|"webgpu"|"cpu"|null} [options.device=null] The device to run the model on.
28
+ * @param {import("@huggingface/transformers").ProgressCallback} [options.progress_callback=null] A callback function that is called with progress information.
29
+ * @returns {Promise<KokoroTTS>} The loaded model
30
+ */
31
+ static async from_pretrained(model_id, { dtype = "fp32", device = null, progress_callback = null } = {}) {
32
+ const model = StyleTextToSpeech2Model.from_pretrained(model_id, { progress_callback, dtype, device });
33
+ const tokenizer = AutoTokenizer.from_pretrained(model_id, { progress_callback });
34
+
35
+ const info = await Promise.all([model, tokenizer]);
36
+ return new KokoroTTS(...info);
37
+ }
38
+
39
+ get voices() {
40
+ return VOICES;
41
+ }
42
+
43
+ list_voices() {
44
+ console.table(VOICES);
45
+ }
46
+
47
+ /**
48
+ * Generate audio from text.
49
+ *
50
+ * Note: The model will be loaded on the first call, and subsequent calls will use the same model.
51
+ * @param {string} text The input text
52
+ * @param {Object} options Additional options
53
+ * @param {keyof typeof VOICES} [options.voice="af"] The voice style to use
54
+ * @param {number} [options.speed=1] The speaking speed
55
+ * @returns {Promise<RawAudio>} The generated audio
56
+ */
57
+ async generate(text, { voice = "af", speed = 1 } = {}) {
58
+ if (!VOICES.hasOwnProperty(voice)) {
59
+ console.error(`Voice "${voice}" not found. Available voices:`);
60
+ console.table(VOICES);
61
+ throw new Error(`Voice "${voice}" not found. Should be one of: ${Object.keys(VOICES).join(", ")}.`);
62
+ }
63
+
64
+ const language = voice.at(0); // "a" or "b"
65
+ const phonemes = await phonemize(text, language);
66
+ const { input_ids } = this.tokenizer(phonemes, {
67
+ truncation: true,
68
+ });
69
+
70
+ // Select voice style based on number of input tokens
71
+ const num_tokens = Math.max(
72
+ input_ids.dims.at(-1) - 2, // Without padding;
73
+ 0,
74
+ );
75
+
76
+ // Load voice style
77
+ const data = await getVoiceData(voice);
78
+ const offset = num_tokens * STYLE_DIM;
79
+ const voiceData = data.slice(offset, offset + STYLE_DIM);
80
+
81
+ // Prepare model inputs
82
+ const inputs = {
83
+ input_ids,
84
+ style: new Tensor("float32", voiceData, [1, STYLE_DIM]),
85
+ speed: new Tensor("float32", [speed], [1]),
86
+ };
87
+
88
+ // Generate audio
89
+ const { waveform } = await this.model(inputs);
90
+
91
+ return new RawAudio(waveform.data, SAMPLE_RATE);
92
+ }
93
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @fileoverview Kokoro TTS provider implementation using Hugging Face transformers
3
+ * Runs on Node.js CPU via transformers library
4
+ */
5
+ import type { TTSResult } from "../types/types";
6
+ import { KOKORO_VOICES, type KokoroVoice } from "../types/types";
7
+
8
+ let ttsInstance: any = null;
9
+ let modelLoading: Promise<any> | null = null;
10
+
11
+ /**
12
+ * Lazy-load Kokoro model using Hugging Face transformers (happens once per server instance)
13
+ */
14
+ async function getKokoroTTS() {
15
+ if (ttsInstance) return ttsInstance;
16
+
17
+ if (modelLoading) {
18
+ await modelLoading;
19
+ return ttsInstance;
20
+ }
21
+
22
+ modelLoading = (async () => {
23
+ try {
24
+ // Dynamic import to load transformers library. The specifier is kept in
25
+ // a variable so the type-checker/bundler treats it as an optional runtime
26
+ // dependency (see optionalDependencies) rather than a build-time one.
27
+ const transformersModule = "@huggingface/transformers";
28
+ const transformers: any = await import(/* @vite-ignore */ transformersModule);
29
+ const { StyleTextToSpeech2Model, AutoTokenizer } = transformers;
30
+
31
+ const model_id = "hexgrad/Kokoro-82M";
32
+
33
+ // Load model and tokenizer in parallel
34
+ const [model, tokenizer] = await Promise.all([
35
+ StyleTextToSpeech2Model.from_pretrained(model_id, {
36
+ device: "cpu",
37
+ dtype: "q8"
38
+ }),
39
+ AutoTokenizer.from_pretrained(model_id)
40
+ ]);
41
+
42
+ ttsInstance = { model, tokenizer };
43
+
44
+ console.log("[Kokoro] Model loaded successfully");
45
+ } catch (error) {
46
+ console.error("[Kokoro] Failed to load model:", error);
47
+ throw error;
48
+ } finally {
49
+ modelLoading = null;
50
+ }
51
+ })();
52
+
53
+ await modelLoading;
54
+ return ttsInstance;
55
+ }
56
+
57
+ /**
58
+ * Generate speech from text using Kokoro
59
+ */
60
+ export async function generateKokoroSpeech(
61
+ text: string,
62
+ voice: string = "af_heart"
63
+ ): Promise<TTSResult> {
64
+ // Validate voice
65
+ const kokoroVoice = KOKORO_VOICES.includes(voice as KokoroVoice)
66
+ ? (voice as KokoroVoice)
67
+ : "af_heart";
68
+
69
+ const tts = await getKokoroTTS();
70
+
71
+ try {
72
+ // For Node.js server-side TTS, we'd need the complete transformers implementation
73
+ // This is a placeholder showing the expected interface
74
+ // Consider using the browser-based implementation via Web Workers for production
75
+
76
+ throw new Error("Server-side Kokoro TTS requires additional setup. Use the browser-based implementation via Web Workers.");
77
+ } catch (error) {
78
+ console.error("[Kokoro] Error generating speech:", error);
79
+ throw error;
80
+ }
81
+ }