@quatrain/voice 1.0.1
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/.turbo/turbo-build.log +0 -0
- package/dist/AbstractVoiceAdapter.d.ts +5 -0
- package/dist/AbstractVoiceAdapter.d.ts.map +1 -0
- package/dist/AbstractVoiceAdapter.js +7 -0
- package/dist/AbstractVoiceAdapter.js.map +1 -0
- package/dist/BrowserVoiceAdapter.d.ts +8 -0
- package/dist/BrowserVoiceAdapter.d.ts.map +1 -0
- package/dist/BrowserVoiceAdapter.js +22 -0
- package/dist/BrowserVoiceAdapter.js.map +1 -0
- package/dist/ElevenLabsVoiceAdapter.d.ts +16 -0
- package/dist/ElevenLabsVoiceAdapter.d.ts.map +1 -0
- package/dist/ElevenLabsVoiceAdapter.js +51 -0
- package/dist/ElevenLabsVoiceAdapter.js.map +1 -0
- package/dist/Voice.d.ts +8 -0
- package/dist/Voice.d.ts.map +1 -0
- package/dist/Voice.js +21 -0
- package/dist/Voice.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +17 -0
- package/src/AbstractVoiceAdapter.ts +16 -0
- package/src/BrowserVoiceAdapter.ts +35 -0
- package/src/ElevenLabsVoiceAdapter.ts +63 -0
- package/src/Voice.ts +41 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +12 -0
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbstractVoiceAdapter.d.ts","sourceRoot":"","sources":["../src/AbstractVoiceAdapter.ts"],"names":[],"mappings":"AAGA,8BAAsB,oBAAoB;IAIvC,QAAQ,CAAC,IAAI,IAAI,IAAI;IAOrB,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAC5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbstractVoiceAdapter.js","sourceRoot":"","sources":["../src/AbstractVoiceAdapter.ts"],"names":[],"mappings":";;;AAGA,MAAsB,oBAAoB;CAYzC;AAZD,oDAYC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter';
|
|
2
|
+
export declare class BrowserVoiceAdapter extends AbstractVoiceAdapter {
|
|
3
|
+
init(): void;
|
|
4
|
+
speak(text: string, options?: {
|
|
5
|
+
lang?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=BrowserVoiceAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserVoiceAdapter.d.ts","sourceRoot":"","sources":["../src/BrowserVoiceAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAK7D,qBAAa,mBAAoB,SAAQ,oBAAoB;IAC1D,IAAI,IAAI,IAAI;IAUN,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkBvE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserVoiceAdapter = void 0;
|
|
4
|
+
const AbstractVoiceAdapter_1 = require("./AbstractVoiceAdapter");
|
|
5
|
+
class BrowserVoiceAdapter extends AbstractVoiceAdapter_1.AbstractVoiceAdapter {
|
|
6
|
+
init() {
|
|
7
|
+
}
|
|
8
|
+
async speak(text, options) {
|
|
9
|
+
if (typeof window === 'undefined' || !window.speechSynthesis) {
|
|
10
|
+
throw new Error('BrowserVoiceAdapter requires a browser environment with SpeechSynthesis support.');
|
|
11
|
+
}
|
|
12
|
+
window.speechSynthesis.cancel();
|
|
13
|
+
const cleanText = text.replace(/[#*`[\]()]/g, '');
|
|
14
|
+
const utterance = new SpeechSynthesisUtterance(cleanText);
|
|
15
|
+
if (options?.lang) {
|
|
16
|
+
utterance.lang = options.lang;
|
|
17
|
+
}
|
|
18
|
+
window.speechSynthesis.speak(utterance);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.BrowserVoiceAdapter = BrowserVoiceAdapter;
|
|
22
|
+
//# sourceMappingURL=BrowserVoiceAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserVoiceAdapter.js","sourceRoot":"","sources":["../src/BrowserVoiceAdapter.ts"],"names":[],"mappings":";;;AAAA,iEAA6D;AAK7D,MAAa,mBAAoB,SAAQ,2CAAoB;IAC1D,IAAI;IAEJ,CAAC;IAQD,KAAK,CAAC,KAAK,CAAC,IAAY,EAAE,OAA2B;QAClD,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAA;QACtG,CAAC;QAGD,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,CAAA;QAG/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;QACjD,MAAM,SAAS,GAAG,IAAI,wBAAwB,CAAC,SAAS,CAAC,CAAA;QAEzD,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;YACjB,SAAS,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;QAChC,CAAC;QAED,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC1C,CAAC;CACH;AA7BD,kDA6BC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter';
|
|
2
|
+
export declare class ElevenLabsVoiceAdapter extends AbstractVoiceAdapter {
|
|
3
|
+
private _apiKey;
|
|
4
|
+
private _voiceId;
|
|
5
|
+
private _modelId;
|
|
6
|
+
constructor(config: {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
voiceId?: string;
|
|
9
|
+
modelId?: string;
|
|
10
|
+
});
|
|
11
|
+
init(): void;
|
|
12
|
+
speak(text: string, options?: {
|
|
13
|
+
voiceId?: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ElevenLabsVoiceAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsVoiceAdapter.d.ts","sourceRoot":"","sources":["../src/ElevenLabsVoiceAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAK7D,qBAAa,sBAAuB,SAAQ,oBAAoB;IAC7D,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,QAAQ,CAAQ;gBAEZ,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAO1E,IAAI,IAAI,IAAI;IAYN,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAiC1E"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElevenLabsVoiceAdapter = void 0;
|
|
4
|
+
const AbstractVoiceAdapter_1 = require("./AbstractVoiceAdapter");
|
|
5
|
+
class ElevenLabsVoiceAdapter extends AbstractVoiceAdapter_1.AbstractVoiceAdapter {
|
|
6
|
+
_apiKey;
|
|
7
|
+
_voiceId;
|
|
8
|
+
_modelId;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super();
|
|
11
|
+
this._apiKey = config.apiKey;
|
|
12
|
+
this._voiceId = config.voiceId || 'bVsJfghVbJypxgwVISO3';
|
|
13
|
+
this._modelId = config.modelId || 'eleven_multilingual_v2';
|
|
14
|
+
}
|
|
15
|
+
init() {
|
|
16
|
+
if (!this._apiKey) {
|
|
17
|
+
throw new Error('ElevenLabsVoiceAdapter requires an apiKey.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async speak(text, options) {
|
|
21
|
+
const voiceId = options?.voiceId || this._voiceId;
|
|
22
|
+
const cleanText = text.replace(/[#*`[\]()]/g, '');
|
|
23
|
+
const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'xi-api-key': this._apiKey,
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
text: cleanText,
|
|
31
|
+
model_id: this._modelId,
|
|
32
|
+
voice_settings: {
|
|
33
|
+
stability: 0.5,
|
|
34
|
+
similarity_boost: 0.75,
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const errText = await response.text();
|
|
40
|
+
throw new Error(`ElevenLabs TTS request failed: ${response.statusText} - ${errText}`);
|
|
41
|
+
}
|
|
42
|
+
const audioBlob = await response.blob();
|
|
43
|
+
if (typeof window !== 'undefined') {
|
|
44
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
45
|
+
const audio = new Audio(audioUrl);
|
|
46
|
+
await audio.play();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.ElevenLabsVoiceAdapter = ElevenLabsVoiceAdapter;
|
|
51
|
+
//# sourceMappingURL=ElevenLabsVoiceAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElevenLabsVoiceAdapter.js","sourceRoot":"","sources":["../src/ElevenLabsVoiceAdapter.ts"],"names":[],"mappings":";;;AAAA,iEAA6D;AAK7D,MAAa,sBAAuB,SAAQ,2CAAoB;IACrD,OAAO,CAAQ;IACf,QAAQ,CAAQ;IAChB,QAAQ,CAAQ;IAExB,YAAY,MAA8D;QACvE,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAA;QAC5B,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,IAAI,sBAAsB,CAAA;QACxD,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,IAAI,wBAAwB,CAAA;IAC7D,CAAC;IAED,IAAI;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;QAChE,CAAC;IACJ,CAAC;IAQD,KAAK,CAAC,KAAK,CAAC,IAAY,EAAE,OAA8B;QACrD,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAA;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;QAEjD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,+CAA+C,OAAO,EAAE,EAAE;YACpF,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACN,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,IAAI,CAAC,OAAO;aAC5B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBAClB,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,cAAc,EAAE;oBACb,SAAS,EAAE,GAAG;oBACd,gBAAgB,EAAE,IAAI;iBACxB;aACH,CAAC;SACJ,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACrC,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,UAAU,MAAM,OAAO,EAAE,CAAC,CAAA;QACxF,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAEvC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;YAC/C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAA;YACjC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;QACrB,CAAC;IACJ,CAAC;CACH;AAzDD,wDAyDC"}
|
package/dist/Voice.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter';
|
|
2
|
+
export declare class Voice {
|
|
3
|
+
protected static _adapter: AbstractVoiceAdapter | null;
|
|
4
|
+
static setAdapter(adapter: AbstractVoiceAdapter): void;
|
|
5
|
+
static getAdapter(): AbstractVoiceAdapter;
|
|
6
|
+
static speak(text: string, options?: any): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=Voice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Voice.d.ts","sourceRoot":"","sources":["../src/Voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAK7D,qBAAa,KAAK;IACf,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAO;WAO/C,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI;WAW/C,UAAU,IAAI,oBAAoB;WAalC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAGjE"}
|
package/dist/Voice.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Voice = void 0;
|
|
4
|
+
class Voice {
|
|
5
|
+
static _adapter = null;
|
|
6
|
+
static setAdapter(adapter) {
|
|
7
|
+
this._adapter = adapter;
|
|
8
|
+
this._adapter.init();
|
|
9
|
+
}
|
|
10
|
+
static getAdapter() {
|
|
11
|
+
if (!this._adapter) {
|
|
12
|
+
throw new Error('Voice adapter has not been configured. Call Voice.setAdapter() first.');
|
|
13
|
+
}
|
|
14
|
+
return this._adapter;
|
|
15
|
+
}
|
|
16
|
+
static speak(text, options) {
|
|
17
|
+
return this.getAdapter().speak(text, options);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.Voice = Voice;
|
|
21
|
+
//# sourceMappingURL=Voice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Voice.js","sourceRoot":"","sources":["../src/Voice.ts"],"names":[],"mappings":";;;AAKA,MAAa,KAAK;IACL,MAAM,CAAC,QAAQ,GAAgC,IAAI,CAAA;IAOtD,MAAM,CAAC,UAAU,CAAC,OAA6B;QACnD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;QACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACvB,CAAC;IAQM,MAAM,CAAC,UAAU;QACrB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAA;QAC3F,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAA;IACvB,CAAC;IAQM,MAAM,CAAC,KAAK,CAAC,IAAY,EAAE,OAAa;QAC5C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAChD,CAAC;;AAlCJ,sBAmCC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Voice = exports.ElevenLabsVoiceAdapter = exports.BrowserVoiceAdapter = exports.AbstractVoiceAdapter = void 0;
|
|
4
|
+
var AbstractVoiceAdapter_1 = require("./AbstractVoiceAdapter");
|
|
5
|
+
Object.defineProperty(exports, "AbstractVoiceAdapter", { enumerable: true, get: function () { return AbstractVoiceAdapter_1.AbstractVoiceAdapter; } });
|
|
6
|
+
var BrowserVoiceAdapter_1 = require("./BrowserVoiceAdapter");
|
|
7
|
+
Object.defineProperty(exports, "BrowserVoiceAdapter", { enumerable: true, get: function () { return BrowserVoiceAdapter_1.BrowserVoiceAdapter; } });
|
|
8
|
+
var ElevenLabsVoiceAdapter_1 = require("./ElevenLabsVoiceAdapter");
|
|
9
|
+
Object.defineProperty(exports, "ElevenLabsVoiceAdapter", { enumerable: true, get: function () { return ElevenLabsVoiceAdapter_1.ElevenLabsVoiceAdapter; } });
|
|
10
|
+
var Voice_1 = require("./Voice");
|
|
11
|
+
Object.defineProperty(exports, "Voice", { enumerable: true, get: function () { return Voice_1.Voice; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+DAA6D;AAApD,4HAAA,oBAAoB,OAAA;AAC7B,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA;AAC5B,mEAAiE;AAAxD,gIAAA,sBAAsB,OAAA;AAC/B,iCAA+B;AAAtB,8FAAA,KAAK,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quatrain/voice",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"typescript": "^5.2.2"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/Quatrain/CoreUX.git",
|
|
15
|
+
"directory": "packages/voice"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract blueprint for voice synthesis adapters.
|
|
3
|
+
*/
|
|
4
|
+
export abstract class AbstractVoiceAdapter {
|
|
5
|
+
/**
|
|
6
|
+
* Adapter specific initialization
|
|
7
|
+
*/
|
|
8
|
+
abstract init(): void
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Synthesize text to speech
|
|
12
|
+
* @param text - The plain text to read aloud
|
|
13
|
+
* @param options - Configuration options such as voice ID, language, speed, etc.
|
|
14
|
+
*/
|
|
15
|
+
abstract speak(text: string, options?: any): Promise<void>
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Voice synthesis adapter using the native Web Speech API (SpeechSynthesis) in the browser.
|
|
5
|
+
*/
|
|
6
|
+
export class BrowserVoiceAdapter extends AbstractVoiceAdapter {
|
|
7
|
+
init(): void {
|
|
8
|
+
// Browser SpeechSynthesis does not need explicit initialization keys
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Synthesize text using the browser's speechSynthesis engine.
|
|
13
|
+
*
|
|
14
|
+
* @param text - Plain text to synthesize.
|
|
15
|
+
* @param options - Configuration options. Supports `lang` (e.g. 'fr-FR', 'en-US').
|
|
16
|
+
*/
|
|
17
|
+
async speak(text: string, options?: { lang?: string }): Promise<void> {
|
|
18
|
+
if (typeof window === 'undefined' || !window.speechSynthesis) {
|
|
19
|
+
throw new Error('BrowserVoiceAdapter requires a browser environment with SpeechSynthesis support.')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Cancel any ongoing speech
|
|
23
|
+
window.speechSynthesis.cancel()
|
|
24
|
+
|
|
25
|
+
// Strip markdown syntax for natural reading
|
|
26
|
+
const cleanText = text.replace(/[#*`[\]()]/g, '')
|
|
27
|
+
const utterance = new SpeechSynthesisUtterance(cleanText)
|
|
28
|
+
|
|
29
|
+
if (options?.lang) {
|
|
30
|
+
utterance.lang = options.lang
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
window.speechSynthesis.speak(utterance)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Voice synthesis adapter using ElevenLabs HTTP API.
|
|
5
|
+
*/
|
|
6
|
+
export class ElevenLabsVoiceAdapter extends AbstractVoiceAdapter {
|
|
7
|
+
private _apiKey: string
|
|
8
|
+
private _voiceId: string
|
|
9
|
+
private _modelId: string
|
|
10
|
+
|
|
11
|
+
constructor(config: { apiKey: string; voiceId?: string; modelId?: string }) {
|
|
12
|
+
super()
|
|
13
|
+
this._apiKey = config.apiKey
|
|
14
|
+
this._voiceId = config.voiceId || 'bVsJfghVbJypxgwVISO3' // User's voice
|
|
15
|
+
this._modelId = config.modelId || 'eleven_multilingual_v2'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
init(): void {
|
|
19
|
+
if (!this._apiKey) {
|
|
20
|
+
throw new Error('ElevenLabsVoiceAdapter requires an apiKey.')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Synthesize text using ElevenLabs API and play the returned audio blob.
|
|
26
|
+
*
|
|
27
|
+
* @param text - Plain text to synthesize.
|
|
28
|
+
* @param options - Configuration options. Supports `voiceId` override.
|
|
29
|
+
*/
|
|
30
|
+
async speak(text: string, options?: { voiceId?: string }): Promise<void> {
|
|
31
|
+
const voiceId = options?.voiceId || this._voiceId
|
|
32
|
+
const cleanText = text.replace(/[#*`[\]()]/g, '')
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'xi-api-key': this._apiKey,
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
text: cleanText,
|
|
42
|
+
model_id: this._modelId,
|
|
43
|
+
voice_settings: {
|
|
44
|
+
stability: 0.5,
|
|
45
|
+
similarity_boost: 0.75,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const errText = await response.text()
|
|
52
|
+
throw new Error(`ElevenLabs TTS request failed: ${response.statusText} - ${errText}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const audioBlob = await response.blob()
|
|
56
|
+
|
|
57
|
+
if (typeof window !== 'undefined') {
|
|
58
|
+
const audioUrl = URL.createObjectURL(audioBlob)
|
|
59
|
+
const audio = new Audio(audioUrl)
|
|
60
|
+
await audio.play()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/Voice.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AbstractVoiceAdapter } from './AbstractVoiceAdapter'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified static registry to configure and trigger the active voice synthesis adapter.
|
|
5
|
+
*/
|
|
6
|
+
export class Voice {
|
|
7
|
+
protected static _adapter: AbstractVoiceAdapter | null = null
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Assign and initialize the active voice synthesis adapter.
|
|
11
|
+
*
|
|
12
|
+
* @param adapter - An instance of AbstractVoiceAdapter.
|
|
13
|
+
*/
|
|
14
|
+
public static setAdapter(adapter: AbstractVoiceAdapter): void {
|
|
15
|
+
this._adapter = adapter
|
|
16
|
+
this._adapter.init()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve the currently active voice adapter.
|
|
21
|
+
*
|
|
22
|
+
* @returns The active AbstractVoiceAdapter.
|
|
23
|
+
* @throws {Error} If no adapter has been set.
|
|
24
|
+
*/
|
|
25
|
+
public static getAdapter(): AbstractVoiceAdapter {
|
|
26
|
+
if (!this._adapter) {
|
|
27
|
+
throw new Error('Voice adapter has not been configured. Call Voice.setAdapter() first.')
|
|
28
|
+
}
|
|
29
|
+
return this._adapter
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Trigger voice synthesis on the active adapter.
|
|
34
|
+
*
|
|
35
|
+
* @param text - Plain text to read.
|
|
36
|
+
* @param options - Configuration options for synthesis.
|
|
37
|
+
*/
|
|
38
|
+
public static speak(text: string, options?: any): Promise<void> {
|
|
39
|
+
return this.getAdapter().speak(text, options)
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED