phonic 0.1.3 → 0.3.0
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 +84 -25
- package/dist/index.d.mts +37 -12
- package/dist/index.d.ts +37 -12
- package/dist/index.js +49 -35
- package/dist/index.mjs +49 -35
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -50,47 +50,106 @@ if (error === null) {
|
|
|
50
50
|
|
|
51
51
|
### Text-to-speech via WebSocket
|
|
52
52
|
|
|
53
|
+
Open a WebSocket connection:
|
|
54
|
+
|
|
53
55
|
```js
|
|
54
|
-
const { data, error } = await phonic.tts.websocket(
|
|
56
|
+
const { data, error } = await phonic.tts.websocket({
|
|
57
|
+
model: "shasta",
|
|
58
|
+
output_format: "mulaw_8000",
|
|
59
|
+
voice_id: "australian-man",
|
|
60
|
+
});
|
|
55
61
|
|
|
56
|
-
if (error
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
if (error !== null) {
|
|
63
|
+
throw new Error(error.message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Here we know that the WebSocket connection is open.
|
|
67
|
+
const { phonicWebSocket } = data;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Process audio chunks that Phonic sends back to you, by sending them to Twilio, for example:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
phonicWebSocket.onMessage((message) => {
|
|
74
|
+
if (message.type === "audio_chunk") {
|
|
75
|
+
ws.send(
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
event: "media",
|
|
78
|
+
streamSid: "...",
|
|
79
|
+
media: {
|
|
80
|
+
payload: message.audio,
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
68
84
|
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Send text chunks to Phonic for audio generation as you receive them from LLM:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
const stream = await openai.chat.completions.create(...);
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
for await (const chunk of stream) {
|
|
94
|
+
const text = chunk.choices[0]?.delta?.content || "";
|
|
95
|
+
|
|
96
|
+
if (text) {
|
|
97
|
+
phonicWebSocket.generate({ text });
|
|
98
|
+
}
|
|
71
99
|
}
|
|
72
100
|
```
|
|
73
101
|
|
|
74
|
-
|
|
102
|
+
Tell Phonic to finish generating audio for all text chunks you've sent:
|
|
75
103
|
|
|
76
104
|
```js
|
|
77
|
-
phonicWebSocket.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
phonicWebSocket.flush();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can also tell Phonic to stop sending audio chunks back, e.g. if the user interrupts the conversation:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
phonicWebSocket.stop();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
To close the WebSocket connection:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
phonicWebSocket.close();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
To know when the last audio chunk has been received:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
phonicWebSocket.onMessage((message) => {
|
|
124
|
+
if (message.type === "flushed") {
|
|
125
|
+
console.log("Last audio chunk received");
|
|
81
126
|
}
|
|
82
127
|
});
|
|
128
|
+
```
|
|
83
129
|
|
|
84
|
-
|
|
85
|
-
script: "How can I help you today?",
|
|
86
|
-
output_format: "mulaw_8000",
|
|
87
|
-
});
|
|
130
|
+
You can also listen for close and error events:
|
|
88
131
|
|
|
89
|
-
|
|
132
|
+
```js
|
|
133
|
+
phonicWebSocket.onClose((event) => {
|
|
134
|
+
console.log(
|
|
135
|
+
`Phonic WebSocket closed with code ${event.code} and reason "${event.reason}"`,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
90
138
|
|
|
91
|
-
|
|
139
|
+
phonicWebSocket.onError((event) => {
|
|
140
|
+
console.log(`Error from Phonic WebSocket: ${event.message}`);
|
|
141
|
+
});
|
|
92
142
|
```
|
|
93
143
|
|
|
144
|
+
## Release a new version to npm
|
|
145
|
+
|
|
146
|
+
1. `bunx changeset`
|
|
147
|
+
2. `git add .`
|
|
148
|
+
3. `git commit -m "Add changeset"`
|
|
149
|
+
4. `git push`
|
|
150
|
+
|
|
151
|
+
Git action will run and create a PR.
|
|
152
|
+
Once this PR is merged, the new version will be released to npm.
|
|
94
153
|
|
|
95
154
|
## License
|
|
96
155
|
|
package/dist/index.d.mts
CHANGED
|
@@ -18,39 +18,64 @@ type DataOrError<T> = Promise<{
|
|
|
18
18
|
error: ErrorResponse;
|
|
19
19
|
}>;
|
|
20
20
|
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
output_format
|
|
21
|
+
type PhonicWebSocketParams = {
|
|
22
|
+
model?: string;
|
|
23
|
+
output_format?: string;
|
|
24
|
+
voice_id?: string;
|
|
24
25
|
};
|
|
25
26
|
type PhonicWebSocketResponseMessage = {
|
|
26
|
-
type: "
|
|
27
|
-
|
|
27
|
+
type: "config";
|
|
28
|
+
model: string;
|
|
29
|
+
output_format: string;
|
|
30
|
+
voice_id: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "audio_chunk";
|
|
33
|
+
audio: string;
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "flush_confirm";
|
|
37
|
+
} | {
|
|
38
|
+
type: "stop_confirm";
|
|
39
|
+
} | {
|
|
40
|
+
type: "error";
|
|
41
|
+
error: {
|
|
28
42
|
message: string;
|
|
29
43
|
code?: string;
|
|
30
44
|
};
|
|
31
45
|
paramErrors?: {
|
|
32
|
-
|
|
46
|
+
model?: string;
|
|
33
47
|
output_format?: string;
|
|
48
|
+
voice_id?: string;
|
|
49
|
+
text?: string;
|
|
50
|
+
speed?: string;
|
|
34
51
|
};
|
|
35
52
|
};
|
|
36
|
-
type OnMessageCallback = (
|
|
53
|
+
type OnMessageCallback = (message: PhonicWebSocketResponseMessage) => void;
|
|
54
|
+
type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
|
|
55
|
+
type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
|
|
37
56
|
|
|
38
57
|
declare class PhonicWebSocket {
|
|
39
58
|
private readonly ws;
|
|
40
59
|
private onMessageCallback;
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
private streamController;
|
|
60
|
+
private onCloseCallback;
|
|
61
|
+
private onErrorCallback;
|
|
44
62
|
constructor(ws: WebSocket);
|
|
45
63
|
onMessage(callback: OnMessageCallback): void;
|
|
46
|
-
|
|
64
|
+
onClose(callback: OnCloseCallback): void;
|
|
65
|
+
onError(callback: OnErrorCallback): void;
|
|
66
|
+
generate(message: {
|
|
67
|
+
text: string;
|
|
68
|
+
speed?: number;
|
|
69
|
+
}): void;
|
|
70
|
+
flush(): void;
|
|
71
|
+
stop(): void;
|
|
47
72
|
close(): void;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
declare class TextToSpeech {
|
|
51
76
|
private readonly phonic;
|
|
52
77
|
constructor(phonic: Phonic);
|
|
53
|
-
websocket(): DataOrError<{
|
|
78
|
+
websocket(params?: PhonicWebSocketParams): DataOrError<{
|
|
54
79
|
phonicWebSocket: PhonicWebSocket;
|
|
55
80
|
}>;
|
|
56
81
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,39 +18,64 @@ type DataOrError<T> = Promise<{
|
|
|
18
18
|
error: ErrorResponse;
|
|
19
19
|
}>;
|
|
20
20
|
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
output_format
|
|
21
|
+
type PhonicWebSocketParams = {
|
|
22
|
+
model?: string;
|
|
23
|
+
output_format?: string;
|
|
24
|
+
voice_id?: string;
|
|
24
25
|
};
|
|
25
26
|
type PhonicWebSocketResponseMessage = {
|
|
26
|
-
type: "
|
|
27
|
-
|
|
27
|
+
type: "config";
|
|
28
|
+
model: string;
|
|
29
|
+
output_format: string;
|
|
30
|
+
voice_id: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "audio_chunk";
|
|
33
|
+
audio: string;
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "flush_confirm";
|
|
37
|
+
} | {
|
|
38
|
+
type: "stop_confirm";
|
|
39
|
+
} | {
|
|
40
|
+
type: "error";
|
|
41
|
+
error: {
|
|
28
42
|
message: string;
|
|
29
43
|
code?: string;
|
|
30
44
|
};
|
|
31
45
|
paramErrors?: {
|
|
32
|
-
|
|
46
|
+
model?: string;
|
|
33
47
|
output_format?: string;
|
|
48
|
+
voice_id?: string;
|
|
49
|
+
text?: string;
|
|
50
|
+
speed?: string;
|
|
34
51
|
};
|
|
35
52
|
};
|
|
36
|
-
type OnMessageCallback = (
|
|
53
|
+
type OnMessageCallback = (message: PhonicWebSocketResponseMessage) => void;
|
|
54
|
+
type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
|
|
55
|
+
type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
|
|
37
56
|
|
|
38
57
|
declare class PhonicWebSocket {
|
|
39
58
|
private readonly ws;
|
|
40
59
|
private onMessageCallback;
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
private streamController;
|
|
60
|
+
private onCloseCallback;
|
|
61
|
+
private onErrorCallback;
|
|
44
62
|
constructor(ws: WebSocket);
|
|
45
63
|
onMessage(callback: OnMessageCallback): void;
|
|
46
|
-
|
|
64
|
+
onClose(callback: OnCloseCallback): void;
|
|
65
|
+
onError(callback: OnErrorCallback): void;
|
|
66
|
+
generate(message: {
|
|
67
|
+
text: string;
|
|
68
|
+
speed?: number;
|
|
69
|
+
}): void;
|
|
70
|
+
flush(): void;
|
|
71
|
+
stop(): void;
|
|
47
72
|
close(): void;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
declare class TextToSpeech {
|
|
51
76
|
private readonly phonic;
|
|
52
77
|
constructor(phonic: Phonic);
|
|
53
|
-
websocket(): DataOrError<{
|
|
78
|
+
websocket(params?: PhonicWebSocketParams): DataOrError<{
|
|
54
79
|
phonicWebSocket: PhonicWebSocket;
|
|
55
80
|
}>;
|
|
56
81
|
}
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ __export(src_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(src_exports);
|
|
36
36
|
|
|
37
37
|
// package.json
|
|
38
|
-
var version = "0.
|
|
38
|
+
var version = "0.3.0";
|
|
39
39
|
|
|
40
40
|
// src/tts/index.ts
|
|
41
41
|
var import_ws = __toESM(require("ws"));
|
|
@@ -45,45 +45,58 @@ var PhonicWebSocket = class {
|
|
|
45
45
|
constructor(ws) {
|
|
46
46
|
this.ws = ws;
|
|
47
47
|
this.ws.onmessage = (event) => {
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.onMessageCallback?.(event.data);
|
|
61
|
-
this.streamController?.enqueue(event.data);
|
|
48
|
+
if (this.onMessageCallback === null) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (typeof event.data !== "string") {
|
|
52
|
+
throw new Error("Received non-string message");
|
|
53
|
+
}
|
|
54
|
+
const dataObj = JSON.parse(event.data);
|
|
55
|
+
this.onMessageCallback(dataObj);
|
|
56
|
+
};
|
|
57
|
+
this.ws.onclose = (event) => {
|
|
58
|
+
if (this.onCloseCallback === null) {
|
|
59
|
+
return;
|
|
62
60
|
}
|
|
61
|
+
this.onCloseCallback(event);
|
|
63
62
|
};
|
|
63
|
+
this.ws.onerror = (event) => {
|
|
64
|
+
if (this.onErrorCallback === null) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.onErrorCallback(event);
|
|
68
|
+
};
|
|
69
|
+
this.onMessage = this.onMessage.bind(this);
|
|
70
|
+
this.generate = this.generate.bind(this);
|
|
71
|
+
this.flush = this.flush.bind(this);
|
|
72
|
+
this.stop = this.stop.bind(this);
|
|
73
|
+
this.close = this.close.bind(this);
|
|
64
74
|
}
|
|
65
75
|
onMessageCallback = null;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
streamEnded = new Promise((resolve) => {
|
|
69
|
-
this.streamEndedResolve = resolve;
|
|
70
|
-
});
|
|
71
|
-
streamController = null;
|
|
76
|
+
onCloseCallback = null;
|
|
77
|
+
onErrorCallback = null;
|
|
72
78
|
onMessage(callback) {
|
|
73
79
|
this.onMessageCallback = callback;
|
|
74
80
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
onClose(callback) {
|
|
82
|
+
this.onCloseCallback = callback;
|
|
83
|
+
}
|
|
84
|
+
onError(callback) {
|
|
85
|
+
this.onErrorCallback = callback;
|
|
86
|
+
}
|
|
87
|
+
generate(message) {
|
|
88
|
+
this.ws.send(
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
type: "generate",
|
|
91
|
+
...message
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
flush() {
|
|
96
|
+
this.ws.send(JSON.stringify({ type: "flush" }));
|
|
97
|
+
}
|
|
98
|
+
stop() {
|
|
99
|
+
this.ws.send(JSON.stringify({ type: "stop" }));
|
|
87
100
|
}
|
|
88
101
|
close() {
|
|
89
102
|
this.ws.close();
|
|
@@ -95,10 +108,11 @@ var TextToSpeech = class {
|
|
|
95
108
|
constructor(phonic) {
|
|
96
109
|
this.phonic = phonic;
|
|
97
110
|
}
|
|
98
|
-
async websocket() {
|
|
111
|
+
async websocket(params) {
|
|
99
112
|
return new Promise((resolve) => {
|
|
100
113
|
const wsBaseUrl = this.phonic.baseUrl.replace(/^http/, "ws");
|
|
101
|
-
const
|
|
114
|
+
const queryString = new URLSearchParams(params).toString();
|
|
115
|
+
const ws = new import_ws.default(`${wsBaseUrl}/v1/tts/ws?${queryString}`, {
|
|
102
116
|
headers: {
|
|
103
117
|
Authorization: `Bearer ${this.phonic.apiKey}`
|
|
104
118
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.3.0";
|
|
3
3
|
|
|
4
4
|
// src/tts/index.ts
|
|
5
5
|
import WebSocket from "ws";
|
|
@@ -9,45 +9,58 @@ var PhonicWebSocket = class {
|
|
|
9
9
|
constructor(ws) {
|
|
10
10
|
this.ws = ws;
|
|
11
11
|
this.ws.onmessage = (event) => {
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.onMessageCallback?.(event.data);
|
|
25
|
-
this.streamController?.enqueue(event.data);
|
|
12
|
+
if (this.onMessageCallback === null) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (typeof event.data !== "string") {
|
|
16
|
+
throw new Error("Received non-string message");
|
|
17
|
+
}
|
|
18
|
+
const dataObj = JSON.parse(event.data);
|
|
19
|
+
this.onMessageCallback(dataObj);
|
|
20
|
+
};
|
|
21
|
+
this.ws.onclose = (event) => {
|
|
22
|
+
if (this.onCloseCallback === null) {
|
|
23
|
+
return;
|
|
26
24
|
}
|
|
25
|
+
this.onCloseCallback(event);
|
|
27
26
|
};
|
|
27
|
+
this.ws.onerror = (event) => {
|
|
28
|
+
if (this.onErrorCallback === null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.onErrorCallback(event);
|
|
32
|
+
};
|
|
33
|
+
this.onMessage = this.onMessage.bind(this);
|
|
34
|
+
this.generate = this.generate.bind(this);
|
|
35
|
+
this.flush = this.flush.bind(this);
|
|
36
|
+
this.stop = this.stop.bind(this);
|
|
37
|
+
this.close = this.close.bind(this);
|
|
28
38
|
}
|
|
29
39
|
onMessageCallback = null;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
streamEnded = new Promise((resolve) => {
|
|
33
|
-
this.streamEndedResolve = resolve;
|
|
34
|
-
});
|
|
35
|
-
streamController = null;
|
|
40
|
+
onCloseCallback = null;
|
|
41
|
+
onErrorCallback = null;
|
|
36
42
|
onMessage(callback) {
|
|
37
43
|
this.onMessageCallback = callback;
|
|
38
44
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
onClose(callback) {
|
|
46
|
+
this.onCloseCallback = callback;
|
|
47
|
+
}
|
|
48
|
+
onError(callback) {
|
|
49
|
+
this.onErrorCallback = callback;
|
|
50
|
+
}
|
|
51
|
+
generate(message) {
|
|
52
|
+
this.ws.send(
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
type: "generate",
|
|
55
|
+
...message
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
flush() {
|
|
60
|
+
this.ws.send(JSON.stringify({ type: "flush" }));
|
|
61
|
+
}
|
|
62
|
+
stop() {
|
|
63
|
+
this.ws.send(JSON.stringify({ type: "stop" }));
|
|
51
64
|
}
|
|
52
65
|
close() {
|
|
53
66
|
this.ws.close();
|
|
@@ -59,10 +72,11 @@ var TextToSpeech = class {
|
|
|
59
72
|
constructor(phonic) {
|
|
60
73
|
this.phonic = phonic;
|
|
61
74
|
}
|
|
62
|
-
async websocket() {
|
|
75
|
+
async websocket(params) {
|
|
63
76
|
return new Promise((resolve) => {
|
|
64
77
|
const wsBaseUrl = this.phonic.baseUrl.replace(/^http/, "ws");
|
|
65
|
-
const
|
|
78
|
+
const queryString = new URLSearchParams(params).toString();
|
|
79
|
+
const ws = new WebSocket(`${wsBaseUrl}/v1/tts/ws?${queryString}`, {
|
|
66
80
|
headers: {
|
|
67
81
|
Authorization: `Bearer ${this.phonic.apiKey}`
|
|
68
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phonic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Phonic Node.js SDK",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@biomejs/biome": "1.9.4",
|
|
40
40
|
"@changesets/changelog-github": "0.5.0",
|
|
41
|
-
"@changesets/cli": "2.27.
|
|
41
|
+
"@changesets/cli": "2.27.11",
|
|
42
42
|
"@types/bun": "1.1.14",
|
|
43
43
|
"tsup": "8.3.5",
|
|
44
44
|
"typescript": "5.7.2",
|
|
45
|
-
"zod": "3.
|
|
45
|
+
"zod": "3.24.1"
|
|
46
46
|
},
|
|
47
47
|
"files": ["dist/**"],
|
|
48
48
|
"author": {
|