@unboundcx/sdk 4.13.97 → 4.13.98
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/package.json +1 -1
- package/services/ai/tts.js +155 -0
- package/services/ai.js +1 -73
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.98",
|
|
4
4
|
"description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { internalRequest } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Text-to-speech. `sdk.ai.tts.*`.
|
|
5
|
+
*
|
|
6
|
+
* @see app1-api src/services/ai/routes/tts.js
|
|
7
|
+
*/
|
|
8
|
+
export class TextToSpeechService {
|
|
9
|
+
constructor(sdk) {
|
|
10
|
+
this.sdk = sdk;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render text to speech and get back a storage id/url (buffered — waits
|
|
15
|
+
* for the whole file). For low-latency playback (e.g. a voice bot) use
|
|
16
|
+
* `stream()` instead.
|
|
17
|
+
* @param {Object} params
|
|
18
|
+
* @param {string} params.text
|
|
19
|
+
* @param {string} [params.voice]
|
|
20
|
+
* @param {string} [params.languageCode]
|
|
21
|
+
* @param {string} [params.ssmlGender]
|
|
22
|
+
* @param {string} [params.audioEncoding]
|
|
23
|
+
* @param {number} [params.speakingRate]
|
|
24
|
+
* @param {number} [params.pitch]
|
|
25
|
+
* @param {number} [params.volumeGainDb]
|
|
26
|
+
* @param {string[]} [params.effectsProfileIds]
|
|
27
|
+
* @param {boolean} [params.createAccessKey]
|
|
28
|
+
* @returns {Promise<Object>} `{ id, storageId, url? }`
|
|
29
|
+
*/
|
|
30
|
+
async create({
|
|
31
|
+
text,
|
|
32
|
+
voice,
|
|
33
|
+
languageCode,
|
|
34
|
+
ssmlGender,
|
|
35
|
+
audioEncoding,
|
|
36
|
+
speakingRate,
|
|
37
|
+
pitch,
|
|
38
|
+
volumeGainDb,
|
|
39
|
+
effectsProfileIds,
|
|
40
|
+
createAccessKey,
|
|
41
|
+
}) {
|
|
42
|
+
this.sdk.validateParams(
|
|
43
|
+
{
|
|
44
|
+
text,
|
|
45
|
+
voice,
|
|
46
|
+
languageCode,
|
|
47
|
+
ssmlGender,
|
|
48
|
+
audioEncoding,
|
|
49
|
+
speakingRate,
|
|
50
|
+
pitch,
|
|
51
|
+
volumeGainDb,
|
|
52
|
+
effectsProfileIds,
|
|
53
|
+
createAccessKey,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
text: { type: 'string', required: true },
|
|
57
|
+
voice: { type: 'string', required: false },
|
|
58
|
+
languageCode: { type: 'string', required: false },
|
|
59
|
+
ssmlGender: { type: 'string', required: false },
|
|
60
|
+
audioEncoding: { type: 'string', required: false },
|
|
61
|
+
speakingRate: { type: 'number', required: false },
|
|
62
|
+
pitch: { type: 'number', required: false },
|
|
63
|
+
volumeGainDb: { type: 'number', required: false },
|
|
64
|
+
effectsProfileIds: { type: 'array', required: false },
|
|
65
|
+
createAccessKey: { type: 'boolean', required: false },
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const ttsData = { text };
|
|
70
|
+
if (voice) ttsData.voice = voice;
|
|
71
|
+
if (languageCode) ttsData.languageCode = languageCode;
|
|
72
|
+
if (ssmlGender) ttsData.ssmlGender = ssmlGender;
|
|
73
|
+
if (audioEncoding) ttsData.audioEncoding = audioEncoding;
|
|
74
|
+
if (speakingRate) ttsData.speakingRate = speakingRate;
|
|
75
|
+
if (pitch) ttsData.pitch = pitch;
|
|
76
|
+
if (volumeGainDb) ttsData.volumeGainDb = volumeGainDb;
|
|
77
|
+
if (effectsProfileIds) ttsData.effectsProfileIds = effectsProfileIds;
|
|
78
|
+
if (createAccessKey) ttsData.createAccessKey = createAccessKey;
|
|
79
|
+
|
|
80
|
+
const params = {
|
|
81
|
+
body: ttsData,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await internalRequest(this.sdk, '/ai/tts', 'POST', params);
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* List available TTS voices
|
|
90
|
+
* @returns {Promise<Object>} { voices: Array, count: number, supportedEncodings: Array, supportedLanguages: Array }
|
|
91
|
+
*/
|
|
92
|
+
async list() {
|
|
93
|
+
const result = await internalRequest(this.sdk, '/ai/tts', 'GET');
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Stream TTS audio as it's generated — first bytes can arrive well before
|
|
99
|
+
* the whole utterance finishes rendering (Groq voices: TTFB ~0.2s).
|
|
100
|
+
* Always renders wav; shares its cache with `create()` when
|
|
101
|
+
* audioEncoding:'wav' is used there.
|
|
102
|
+
*
|
|
103
|
+
* Node: wrap the result in `Readable.fromWeb(result.body)` to get a
|
|
104
|
+
* normal Node stream (e.g. to pipe into ffmpeg). Browser: `result.body`
|
|
105
|
+
* is already a web ReadableStream you can read directly or hand to
|
|
106
|
+
* `new Response(result.body)` / a `<audio>` element via a Blob.
|
|
107
|
+
*
|
|
108
|
+
* Response headers of note: `x-tts-cache` (`hit`|`miss`) and `x-tts-id`
|
|
109
|
+
* (set on a cache hit, when the id is already known).
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} params
|
|
112
|
+
* @param {string} params.text
|
|
113
|
+
* @param {string} [params.voice]
|
|
114
|
+
* @param {string} [params.languageCode]
|
|
115
|
+
* @returns {Promise<{body: ReadableStream, headers: Headers, status: number}>}
|
|
116
|
+
* @example
|
|
117
|
+
* const result = await sdk.ai.tts.stream({ text: 'Hi there', voice: 'hannah' });
|
|
118
|
+
* const nodeStream = Readable.fromWeb(result.body);
|
|
119
|
+
* nodeStream.pipe(ffmpegProcess.stdin);
|
|
120
|
+
*/
|
|
121
|
+
async stream({ text, voice, languageCode }) {
|
|
122
|
+
this.sdk.validateParams(
|
|
123
|
+
{ text, voice, languageCode },
|
|
124
|
+
{
|
|
125
|
+
text: { type: 'string', required: true },
|
|
126
|
+
voice: { type: 'string', required: false },
|
|
127
|
+
languageCode: { type: 'string', required: false },
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const ttsData = { text };
|
|
132
|
+
if (voice) ttsData.voice = voice;
|
|
133
|
+
if (languageCode) ttsData.languageCode = languageCode;
|
|
134
|
+
|
|
135
|
+
const params = {
|
|
136
|
+
body: ttsData,
|
|
137
|
+
returnRawResponse: true,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// forceFetch: true — NATS transport can't carry a streamed body.
|
|
141
|
+
const response = await internalRequest(
|
|
142
|
+
this.sdk,
|
|
143
|
+
'/ai/tts/stream',
|
|
144
|
+
'POST',
|
|
145
|
+
params,
|
|
146
|
+
true,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
body: response.body,
|
|
151
|
+
headers: response.headers,
|
|
152
|
+
status: response.status,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
package/services/ai.js
CHANGED
|
@@ -5,6 +5,7 @@ import { AssistService } from './ai/assist.js';
|
|
|
5
5
|
import { VocabularyService } from './ai/vocabulary.js';
|
|
6
6
|
import { EmailService } from './ai/email.js';
|
|
7
7
|
import { ModelsService } from './ai/models.js';
|
|
8
|
+
import { TextToSpeechService } from './ai/tts.js';
|
|
8
9
|
import { translate as translateItems } from './ai/translate.js';
|
|
9
10
|
import {
|
|
10
11
|
getSettings as getAiSettings,
|
|
@@ -487,79 +488,6 @@ export class GenerativeService {
|
|
|
487
488
|
// }
|
|
488
489
|
}
|
|
489
490
|
|
|
490
|
-
export class TextToSpeechService {
|
|
491
|
-
constructor(sdk) {
|
|
492
|
-
this.sdk = sdk;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
async create({
|
|
496
|
-
text,
|
|
497
|
-
voice,
|
|
498
|
-
languageCode,
|
|
499
|
-
ssmlGender,
|
|
500
|
-
audioEncoding,
|
|
501
|
-
speakingRate,
|
|
502
|
-
pitch,
|
|
503
|
-
volumeGainDb,
|
|
504
|
-
effectsProfileIds,
|
|
505
|
-
createAccessKey,
|
|
506
|
-
}) {
|
|
507
|
-
this.sdk.validateParams(
|
|
508
|
-
{
|
|
509
|
-
text,
|
|
510
|
-
voice,
|
|
511
|
-
languageCode,
|
|
512
|
-
ssmlGender,
|
|
513
|
-
audioEncoding,
|
|
514
|
-
speakingRate,
|
|
515
|
-
pitch,
|
|
516
|
-
volumeGainDb,
|
|
517
|
-
effectsProfileIds,
|
|
518
|
-
createAccessKey,
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
text: { type: 'string', required: true },
|
|
522
|
-
voice: { type: 'string', required: false },
|
|
523
|
-
languageCode: { type: 'string', required: false },
|
|
524
|
-
ssmlGender: { type: 'string', required: false },
|
|
525
|
-
audioEncoding: { type: 'string', required: false },
|
|
526
|
-
speakingRate: { type: 'number', required: false },
|
|
527
|
-
pitch: { type: 'number', required: false },
|
|
528
|
-
volumeGainDb: { type: 'number', required: false },
|
|
529
|
-
effectsProfileIds: { type: 'array', required: false },
|
|
530
|
-
createAccessKey: { type: 'boolean', required: false },
|
|
531
|
-
},
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
const ttsData = { text };
|
|
535
|
-
if (voice) ttsData.voice = voice;
|
|
536
|
-
if (languageCode) ttsData.languageCode = languageCode;
|
|
537
|
-
if (ssmlGender) ttsData.ssmlGender = ssmlGender;
|
|
538
|
-
if (audioEncoding) ttsData.audioEncoding = audioEncoding;
|
|
539
|
-
if (speakingRate) ttsData.speakingRate = speakingRate;
|
|
540
|
-
if (pitch) ttsData.pitch = pitch;
|
|
541
|
-
if (volumeGainDb) ttsData.volumeGainDb = volumeGainDb;
|
|
542
|
-
if (effectsProfileIds) ttsData.effectsProfileIds = effectsProfileIds;
|
|
543
|
-
if (createAccessKey) ttsData.createAccessKey = createAccessKey;
|
|
544
|
-
|
|
545
|
-
const params = {
|
|
546
|
-
body: ttsData,
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
const result = await internalRequest(this.sdk, '/ai/tts', 'POST', params);
|
|
550
|
-
return result;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* List available TTS voices
|
|
555
|
-
* @returns {Promise<Object>} { voices: Array, count: number, supportedEncodings: Array, supportedLanguages: Array }
|
|
556
|
-
*/
|
|
557
|
-
async list() {
|
|
558
|
-
const result = await internalRequest(this.sdk, '/ai/tts', 'GET');
|
|
559
|
-
return result;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
491
|
export class SpeechToTextService {
|
|
564
492
|
constructor(sdk) {
|
|
565
493
|
this.sdk = sdk;
|