@gravity-rail/sdk 0.5.0 → 0.6.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/dist/agui.cjs +78 -0
- package/dist/agui.cjs.map +1 -0
- package/dist/agui.d.cts +13 -0
- package/dist/agui.d.ts +13 -0
- package/dist/agui.js +48 -0
- package/dist/agui.js.map +1 -0
- package/dist/audio.cjs +223 -0
- package/dist/audio.cjs.map +1 -0
- package/dist/audio.d.cts +25 -0
- package/dist/audio.d.ts +25 -0
- package/dist/audio.js +147 -0
- package/dist/audio.js.map +1 -0
- package/dist/chunk-BSOOMHW6.js +62 -0
- package/dist/chunk-BSOOMHW6.js.map +1 -0
- package/dist/{chunk-QZC4QPP2.js → chunk-DX42AMVM.js} +2 -31
- package/dist/chunk-DX42AMVM.js.map +1 -0
- package/dist/{index-CIwgGcAm.d.ts → client-BKGhDv14.d.cts} +37 -1617
- package/dist/{index-DNxSoo5S.d.cts → client-dP29995A.d.ts} +37 -1617
- package/dist/helpers.cjs +123 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.d.cts +28 -0
- package/dist/helpers.d.ts +28 -0
- package/dist/helpers.js +92 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index-CBfaxd3y.d.cts +1589 -0
- package/dist/index-Q5qU2JFt.d.ts +1589 -0
- package/dist/index.cjs +1 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/{inquiries-DiTDrTG2.d.cts → inquiries-BH6I8-SF.d.cts} +1 -1
- package/dist/{inquiries-DiTDrTG2.d.ts → inquiries-BH6I8-SF.d.ts} +1 -1
- package/dist/internal.cjs +1 -30
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +4 -3
- package/dist/internal.d.ts +4 -3
- package/dist/internal.js +1 -1
- package/dist/oauth-client.cjs +78 -0
- package/dist/oauth-client.cjs.map +1 -0
- package/dist/oauth-client.d.cts +15 -0
- package/dist/oauth-client.d.ts +15 -0
- package/dist/oauth-client.js +48 -0
- package/dist/oauth-client.js.map +1 -0
- package/dist/realtime.cjs +637 -0
- package/dist/realtime.cjs.map +1 -0
- package/dist/realtime.d.cts +94 -0
- package/dist/realtime.d.ts +94 -0
- package/dist/realtime.js +580 -0
- package/dist/realtime.js.map +1 -0
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/types-DkYyrvoC.d.cts +19 -0
- package/dist/types-DkYyrvoC.d.ts +19 -0
- package/package.json +27 -2
- package/dist/chunk-QZC4QPP2.js.map +0 -1
package/dist/agui.cjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/agui/index.ts
|
|
21
|
+
var agui_exports = {};
|
|
22
|
+
__export(agui_exports, {
|
|
23
|
+
applyAguiTextMessageEvent: () => applyAguiTextMessageEvent,
|
|
24
|
+
getAguiTextMessageContent: () => getAguiTextMessageContent,
|
|
25
|
+
getAguiTextMessageId: () => getAguiTextMessageId,
|
|
26
|
+
getAguiTextMessageRole: () => getAguiTextMessageRole
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(agui_exports);
|
|
29
|
+
|
|
30
|
+
// src/agui/text-messages.ts
|
|
31
|
+
function getAguiTextMessageId(event) {
|
|
32
|
+
const raw = event.messageId ?? event.message_id;
|
|
33
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
34
|
+
}
|
|
35
|
+
function getAguiTextMessageContent(event) {
|
|
36
|
+
const raw = event.content ?? event.text ?? event.delta;
|
|
37
|
+
return typeof raw === "string" ? raw : null;
|
|
38
|
+
}
|
|
39
|
+
function getAguiTextMessageRole(event, fallback = "assistant") {
|
|
40
|
+
const raw = event.role;
|
|
41
|
+
return typeof raw === "string" && raw.length > 0 ? raw : fallback;
|
|
42
|
+
}
|
|
43
|
+
function applyAguiTextMessageEvent(messages, event) {
|
|
44
|
+
const type = event.type;
|
|
45
|
+
if (typeof type !== "string") return false;
|
|
46
|
+
if (type === "TEXT_MESSAGE_START") {
|
|
47
|
+
const messageId = getAguiTextMessageId(event);
|
|
48
|
+
if (!messageId) return false;
|
|
49
|
+
const role = getAguiTextMessageRole(event);
|
|
50
|
+
messages.set(messageId, { id: messageId, role, content: "" });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (type === "TEXT_MESSAGE_CONTENT") {
|
|
54
|
+
const messageId = getAguiTextMessageId(event);
|
|
55
|
+
const content = getAguiTextMessageContent(event);
|
|
56
|
+
if (!messageId || content === null) return false;
|
|
57
|
+
const existing = messages.get(messageId) ?? {
|
|
58
|
+
id: messageId,
|
|
59
|
+
role: getAguiTextMessageRole(event),
|
|
60
|
+
content: ""
|
|
61
|
+
};
|
|
62
|
+
existing.content = content;
|
|
63
|
+
messages.set(messageId, existing);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (type === "TEXT_MESSAGE_END") {
|
|
67
|
+
return Boolean(getAguiTextMessageId(event));
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
72
|
+
0 && (module.exports = {
|
|
73
|
+
applyAguiTextMessageEvent,
|
|
74
|
+
getAguiTextMessageContent,
|
|
75
|
+
getAguiTextMessageId,
|
|
76
|
+
getAguiTextMessageRole
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=agui.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agui/index.ts","../src/agui/text-messages.ts"],"sourcesContent":["export {\n applyAguiTextMessageEvent,\n getAguiTextMessageContent,\n getAguiTextMessageId,\n getAguiTextMessageRole,\n type TranscriptMessage,\n} from './text-messages';\n","import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n messages.set(messageId, { id: messageId, role, content: '' });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,aAAS,IAAI,WAAW,EAAE,IAAI,WAAW,MAAM,SAAS,GAAG,CAAC;AAC5D,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAE3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;","names":[]}
|
package/dist/agui.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { C as ChannelEvent } from './types-DkYyrvoC.cjs';
|
|
2
|
+
|
|
3
|
+
interface TranscriptMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
role: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
declare function getAguiTextMessageId(event: ChannelEvent): string | null;
|
|
9
|
+
declare function getAguiTextMessageContent(event: ChannelEvent): string | null;
|
|
10
|
+
declare function getAguiTextMessageRole(event: ChannelEvent, fallback?: string): string;
|
|
11
|
+
declare function applyAguiTextMessageEvent(messages: Map<string, TranscriptMessage>, event: ChannelEvent): boolean;
|
|
12
|
+
|
|
13
|
+
export { type TranscriptMessage, applyAguiTextMessageEvent, getAguiTextMessageContent, getAguiTextMessageId, getAguiTextMessageRole };
|
package/dist/agui.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { C as ChannelEvent } from './types-DkYyrvoC.js';
|
|
2
|
+
|
|
3
|
+
interface TranscriptMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
role: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
declare function getAguiTextMessageId(event: ChannelEvent): string | null;
|
|
9
|
+
declare function getAguiTextMessageContent(event: ChannelEvent): string | null;
|
|
10
|
+
declare function getAguiTextMessageRole(event: ChannelEvent, fallback?: string): string;
|
|
11
|
+
declare function applyAguiTextMessageEvent(messages: Map<string, TranscriptMessage>, event: ChannelEvent): boolean;
|
|
12
|
+
|
|
13
|
+
export { type TranscriptMessage, applyAguiTextMessageEvent, getAguiTextMessageContent, getAguiTextMessageId, getAguiTextMessageRole };
|
package/dist/agui.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/agui/text-messages.ts
|
|
2
|
+
function getAguiTextMessageId(event) {
|
|
3
|
+
const raw = event.messageId ?? event.message_id;
|
|
4
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
5
|
+
}
|
|
6
|
+
function getAguiTextMessageContent(event) {
|
|
7
|
+
const raw = event.content ?? event.text ?? event.delta;
|
|
8
|
+
return typeof raw === "string" ? raw : null;
|
|
9
|
+
}
|
|
10
|
+
function getAguiTextMessageRole(event, fallback = "assistant") {
|
|
11
|
+
const raw = event.role;
|
|
12
|
+
return typeof raw === "string" && raw.length > 0 ? raw : fallback;
|
|
13
|
+
}
|
|
14
|
+
function applyAguiTextMessageEvent(messages, event) {
|
|
15
|
+
const type = event.type;
|
|
16
|
+
if (typeof type !== "string") return false;
|
|
17
|
+
if (type === "TEXT_MESSAGE_START") {
|
|
18
|
+
const messageId = getAguiTextMessageId(event);
|
|
19
|
+
if (!messageId) return false;
|
|
20
|
+
const role = getAguiTextMessageRole(event);
|
|
21
|
+
messages.set(messageId, { id: messageId, role, content: "" });
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (type === "TEXT_MESSAGE_CONTENT") {
|
|
25
|
+
const messageId = getAguiTextMessageId(event);
|
|
26
|
+
const content = getAguiTextMessageContent(event);
|
|
27
|
+
if (!messageId || content === null) return false;
|
|
28
|
+
const existing = messages.get(messageId) ?? {
|
|
29
|
+
id: messageId,
|
|
30
|
+
role: getAguiTextMessageRole(event),
|
|
31
|
+
content: ""
|
|
32
|
+
};
|
|
33
|
+
existing.content = content;
|
|
34
|
+
messages.set(messageId, existing);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (type === "TEXT_MESSAGE_END") {
|
|
38
|
+
return Boolean(getAguiTextMessageId(event));
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
applyAguiTextMessageEvent,
|
|
44
|
+
getAguiTextMessageContent,
|
|
45
|
+
getAguiTextMessageId,
|
|
46
|
+
getAguiTextMessageRole
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=agui.js.map
|
package/dist/agui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agui/text-messages.ts"],"sourcesContent":["import type { ChannelEvent } from '../realtime/types';\n\nexport interface TranscriptMessage {\n id: string;\n role: string;\n content: string;\n}\n\nexport function getAguiTextMessageId(event: ChannelEvent): string | null {\n const raw = event.messageId ?? event.message_id;\n return typeof raw === 'string' && raw.length > 0 ? raw : null;\n}\n\nexport function getAguiTextMessageContent(event: ChannelEvent): string | null {\n const raw = event.content ?? event.text ?? event.delta;\n return typeof raw === 'string' ? raw : null;\n}\n\nexport function getAguiTextMessageRole(event: ChannelEvent, fallback = 'assistant'): string {\n const raw = event.role;\n return typeof raw === 'string' && raw.length > 0 ? raw : fallback;\n}\n\nexport function applyAguiTextMessageEvent(\n messages: Map<string, TranscriptMessage>,\n event: ChannelEvent\n): boolean {\n const type = event.type;\n if (typeof type !== 'string') return false;\n\n if (type === 'TEXT_MESSAGE_START') {\n const messageId = getAguiTextMessageId(event);\n if (!messageId) return false;\n const role = getAguiTextMessageRole(event);\n messages.set(messageId, { id: messageId, role, content: '' });\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_CONTENT') {\n const messageId = getAguiTextMessageId(event);\n const content = getAguiTextMessageContent(event);\n if (!messageId || content === null) return false;\n\n const existing =\n messages.get(messageId) ??\n ({\n id: messageId,\n role: getAguiTextMessageRole(event),\n content: '',\n } satisfies TranscriptMessage);\n\n existing.content = content;\n messages.set(messageId, existing);\n return true;\n }\n\n if (type === 'TEXT_MESSAGE_END') {\n return Boolean(getAguiTextMessageId(event));\n }\n\n return false;\n}\n"],"mappings":";AAQO,SAAS,qBAAqB,OAAoC;AACtE,QAAM,MAAM,MAAM,aAAa,MAAM;AACrC,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BAA0B,OAAoC;AAC3E,QAAM,MAAM,MAAM,WAAW,MAAM,QAAQ,MAAM;AACjD,SAAO,OAAO,QAAQ,WAAW,MAAM;AAC1C;AAEO,SAAS,uBAAuB,OAAqB,WAAW,aAAqB;AACzF,QAAM,MAAM,MAAM;AAClB,SAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,MAAM;AAC5D;AAEO,SAAS,0BACb,UACA,OACQ;AACR,QAAM,OAAO,MAAM;AACnB,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,MAAI,SAAS,sBAAsB;AAChC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,OAAO,uBAAuB,KAAK;AACzC,aAAS,IAAI,WAAW,EAAE,IAAI,WAAW,MAAM,SAAS,GAAG,CAAC;AAC5D,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,wBAAwB;AAClC,UAAM,YAAY,qBAAqB,KAAK;AAC5C,UAAM,UAAU,0BAA0B,KAAK;AAC/C,QAAI,CAAC,aAAa,YAAY,KAAM,QAAO;AAE3C,UAAM,WACH,SAAS,IAAI,SAAS,KACrB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,uBAAuB,KAAK;AAAA,MAClC,SAAS;AAAA,IACZ;AAEH,aAAS,UAAU;AACnB,aAAS,IAAI,WAAW,QAAQ;AAChC,WAAO;AAAA,EACV;AAEA,MAAI,SAAS,oBAAoB;AAC9B,WAAO,QAAQ,qBAAqB,KAAK,CAAC;AAAA,EAC7C;AAEA,SAAO;AACV;","names":[]}
|
package/dist/audio.cjs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/audio/index.ts
|
|
21
|
+
var audio_exports = {};
|
|
22
|
+
__export(audio_exports, {
|
|
23
|
+
MONITOR_SAMPLE_RATE: () => MONITOR_SAMPLE_RATE,
|
|
24
|
+
SERVER_SAMPLE_RATE: () => SERVER_SAMPLE_RATE,
|
|
25
|
+
TARGET_SAMPLE_RATE: () => TARGET_SAMPLE_RATE,
|
|
26
|
+
base64ToFloat32Array: () => base64ToFloat32Array,
|
|
27
|
+
concatFloat32Arrays: () => concatFloat32Arrays,
|
|
28
|
+
createMonitorAudioPlayer: () => createMonitorAudioPlayer,
|
|
29
|
+
downsampleAudio: () => downsampleAudio,
|
|
30
|
+
float32ArrayToBase64: () => float32ArrayToBase64
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(audio_exports);
|
|
33
|
+
|
|
34
|
+
// src/audio/pcm.ts
|
|
35
|
+
var SERVER_SAMPLE_RATE = 24e3;
|
|
36
|
+
var TARGET_SAMPLE_RATE = 24e3;
|
|
37
|
+
var MONITOR_SAMPLE_RATE = 16e3;
|
|
38
|
+
function float32ArrayToBase64(float32Array) {
|
|
39
|
+
const int16Array = new Int16Array(float32Array.length);
|
|
40
|
+
for (let i = 0; i < float32Array.length; i += 1) {
|
|
41
|
+
const sample = Math.max(-1, Math.min(1, float32Array[i]));
|
|
42
|
+
int16Array[i] = Math.round(sample * 32767);
|
|
43
|
+
}
|
|
44
|
+
const uint8Array = new Uint8Array(int16Array.buffer);
|
|
45
|
+
let binaryString = "";
|
|
46
|
+
for (let i = 0; i < uint8Array.length; i += 1) {
|
|
47
|
+
binaryString += String.fromCharCode(uint8Array[i]);
|
|
48
|
+
}
|
|
49
|
+
return btoa(binaryString);
|
|
50
|
+
}
|
|
51
|
+
function base64ToFloat32Array(base64) {
|
|
52
|
+
const binaryString = atob(base64);
|
|
53
|
+
const len = binaryString.length / 2;
|
|
54
|
+
const float32Array = new Float32Array(len);
|
|
55
|
+
for (let i = 0; i < len; i += 1) {
|
|
56
|
+
const lo = binaryString.charCodeAt(i * 2);
|
|
57
|
+
const hi = binaryString.charCodeAt(i * 2 + 1);
|
|
58
|
+
let int16 = hi << 8 | lo;
|
|
59
|
+
if (int16 >= 32768) int16 -= 65536;
|
|
60
|
+
float32Array[i] = int16 / 32768;
|
|
61
|
+
}
|
|
62
|
+
return float32Array;
|
|
63
|
+
}
|
|
64
|
+
function concatFloat32Arrays(arrays) {
|
|
65
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
66
|
+
const result = new Float32Array(totalLength);
|
|
67
|
+
let offset = 0;
|
|
68
|
+
for (const arr of arrays) {
|
|
69
|
+
result.set(arr, offset);
|
|
70
|
+
offset += arr.length;
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function downsampleAudio(audioData, originalSampleRate, targetSampleRate) {
|
|
75
|
+
if (originalSampleRate === targetSampleRate) return audioData;
|
|
76
|
+
const ratio = originalSampleRate / targetSampleRate;
|
|
77
|
+
const newLength = Math.round(audioData.length / ratio);
|
|
78
|
+
const result = new Float32Array(newLength);
|
|
79
|
+
for (let i = 0; i < newLength; i += 1) {
|
|
80
|
+
const srcIndex = Math.floor(i * ratio);
|
|
81
|
+
result[i] = audioData[srcIndex];
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/audio/monitor-player.ts
|
|
87
|
+
function isMonitorAudioEvent(event) {
|
|
88
|
+
return event.type === "CUSTOM" && event.name === "voice.monitor_audio";
|
|
89
|
+
}
|
|
90
|
+
function isInterruptEvent(event) {
|
|
91
|
+
return event.type === "CUSTOM" && event.name === "voice.interrupt";
|
|
92
|
+
}
|
|
93
|
+
function isCallEndEvent(event) {
|
|
94
|
+
return event.type === "CUSTOM" && event.name === "voice.call_end";
|
|
95
|
+
}
|
|
96
|
+
function base64ToUint8Array(base64) {
|
|
97
|
+
const binary = atob(base64);
|
|
98
|
+
const bytes = new Uint8Array(binary.length);
|
|
99
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
100
|
+
bytes[i] = binary.charCodeAt(i);
|
|
101
|
+
}
|
|
102
|
+
return bytes;
|
|
103
|
+
}
|
|
104
|
+
function decodePcm16IntoChannel(bytes, channel) {
|
|
105
|
+
const evenLen = bytes.length - bytes.length % 2;
|
|
106
|
+
const samples = evenLen / 2;
|
|
107
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, evenLen);
|
|
108
|
+
for (let i = 0; i < samples; i += 1) {
|
|
109
|
+
channel[i] = view.getInt16(i * 2, true) / 32768;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function createMonitorAudioPlayer() {
|
|
113
|
+
let audioContext = null;
|
|
114
|
+
const nextStartTime = { user: 0, assistant: 0 };
|
|
115
|
+
let playing = false;
|
|
116
|
+
const assistantSources = [];
|
|
117
|
+
const ensureContext = () => {
|
|
118
|
+
if (typeof window === "undefined") return null;
|
|
119
|
+
if (!audioContext) {
|
|
120
|
+
const Ctx = window.AudioContext ?? window.webkitAudioContext;
|
|
121
|
+
if (!Ctx) return null;
|
|
122
|
+
audioContext = new Ctx({ sampleRate: MONITOR_SAMPLE_RATE });
|
|
123
|
+
nextStartTime.user = 0;
|
|
124
|
+
nextStartTime.assistant = 0;
|
|
125
|
+
}
|
|
126
|
+
if (audioContext.state === "suspended") {
|
|
127
|
+
void audioContext.resume();
|
|
128
|
+
}
|
|
129
|
+
return audioContext;
|
|
130
|
+
};
|
|
131
|
+
const prepare = () => {
|
|
132
|
+
ensureContext();
|
|
133
|
+
};
|
|
134
|
+
const handleEvent = (event) => {
|
|
135
|
+
if (isInterruptEvent(event)) {
|
|
136
|
+
for (const src of assistantSources) {
|
|
137
|
+
try {
|
|
138
|
+
src.stop();
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
assistantSources.length = 0;
|
|
143
|
+
nextStartTime.assistant = 0;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (isCallEndEvent(event)) {
|
|
147
|
+
for (const src of assistantSources) {
|
|
148
|
+
try {
|
|
149
|
+
src.stop();
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
assistantSources.length = 0;
|
|
154
|
+
nextStartTime.user = 0;
|
|
155
|
+
nextStartTime.assistant = 0;
|
|
156
|
+
playing = false;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!isMonitorAudioEvent(event)) return;
|
|
160
|
+
const { data, sampleRate, format, role } = event.value;
|
|
161
|
+
if (format !== "pcm16") return;
|
|
162
|
+
if (role !== "user" && role !== "assistant") return;
|
|
163
|
+
const ctx = ensureContext();
|
|
164
|
+
if (!ctx) return;
|
|
165
|
+
const bytes = base64ToUint8Array(data);
|
|
166
|
+
const sampleCount = Math.floor(bytes.length / 2);
|
|
167
|
+
if (sampleCount === 0) return;
|
|
168
|
+
const buffer = ctx.createBuffer(1, sampleCount, sampleRate || MONITOR_SAMPLE_RATE);
|
|
169
|
+
decodePcm16IntoChannel(bytes, buffer.getChannelData(0));
|
|
170
|
+
const source = ctx.createBufferSource();
|
|
171
|
+
source.buffer = buffer;
|
|
172
|
+
source.connect(ctx.destination);
|
|
173
|
+
const now = ctx.currentTime;
|
|
174
|
+
const cursor = nextStartTime[role];
|
|
175
|
+
const startAt = cursor < now + 0.02 ? now + 0.02 : cursor;
|
|
176
|
+
source.start(startAt);
|
|
177
|
+
nextStartTime[role] = startAt + buffer.duration;
|
|
178
|
+
playing = true;
|
|
179
|
+
if (role === "assistant") {
|
|
180
|
+
assistantSources.push(source);
|
|
181
|
+
source.onended = () => {
|
|
182
|
+
const index = assistantSources.indexOf(source);
|
|
183
|
+
if (index >= 0) assistantSources.splice(index, 1);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const dispose = () => {
|
|
188
|
+
for (const src of assistantSources) {
|
|
189
|
+
try {
|
|
190
|
+
src.stop();
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
assistantSources.length = 0;
|
|
195
|
+
const ctx = audioContext;
|
|
196
|
+
audioContext = null;
|
|
197
|
+
nextStartTime.user = 0;
|
|
198
|
+
nextStartTime.assistant = 0;
|
|
199
|
+
playing = false;
|
|
200
|
+
if (ctx && ctx.state !== "closed") {
|
|
201
|
+
void ctx.close().catch(() => {
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
prepare,
|
|
207
|
+
handleEvent,
|
|
208
|
+
dispose,
|
|
209
|
+
isPlaying: () => playing
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
MONITOR_SAMPLE_RATE,
|
|
215
|
+
SERVER_SAMPLE_RATE,
|
|
216
|
+
TARGET_SAMPLE_RATE,
|
|
217
|
+
base64ToFloat32Array,
|
|
218
|
+
concatFloat32Arrays,
|
|
219
|
+
createMonitorAudioPlayer,
|
|
220
|
+
downsampleAudio,
|
|
221
|
+
float32ArrayToBase64
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=audio.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audio/index.ts","../src/audio/pcm.ts","../src/audio/monitor-player.ts"],"sourcesContent":["export {\n SERVER_SAMPLE_RATE,\n TARGET_SAMPLE_RATE,\n MONITOR_SAMPLE_RATE,\n float32ArrayToBase64,\n base64ToFloat32Array,\n concatFloat32Arrays,\n downsampleAudio,\n} from './pcm';\nexport { createMonitorAudioPlayer } from './monitor-player';\nexport type { MonitorAudioPlayer } from './monitor-player';\n","/** PCM16 helpers aligned with main-app web voice (24 kHz). */\n\nexport const SERVER_SAMPLE_RATE = 24000;\nexport const TARGET_SAMPLE_RATE = 24000;\nexport const MONITOR_SAMPLE_RATE = 16000;\n\nexport function float32ArrayToBase64(float32Array: Float32Array): string {\n const int16Array = new Int16Array(float32Array.length);\n for (let i = 0; i < float32Array.length; i += 1) {\n const sample = Math.max(-1, Math.min(1, float32Array[i]));\n int16Array[i] = Math.round(sample * 32767);\n }\n const uint8Array = new Uint8Array(int16Array.buffer);\n let binaryString = '';\n for (let i = 0; i < uint8Array.length; i += 1) {\n binaryString += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binaryString);\n}\n\nexport function base64ToFloat32Array(base64: string): Float32Array {\n const binaryString = atob(base64);\n const len = binaryString.length / 2;\n const float32Array = new Float32Array(len);\n for (let i = 0; i < len; i += 1) {\n const lo = binaryString.charCodeAt(i * 2);\n const hi = binaryString.charCodeAt(i * 2 + 1);\n let int16 = (hi << 8) | lo;\n if (int16 >= 0x8000) int16 -= 0x10000;\n float32Array[i] = int16 / 32768;\n }\n return float32Array;\n}\n\nexport function concatFloat32Arrays(arrays: Float32Array[]): Float32Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Float32Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\nexport function downsampleAudio(\n audioData: Float32Array,\n originalSampleRate: number,\n targetSampleRate: number\n): Float32Array {\n if (originalSampleRate === targetSampleRate) return audioData;\n const ratio = originalSampleRate / targetSampleRate;\n const newLength = Math.round(audioData.length / ratio);\n const result = new Float32Array(newLength);\n for (let i = 0; i < newLength; i += 1) {\n const srcIndex = Math.floor(i * ratio);\n result[i] = audioData[srcIndex];\n }\n return result;\n}\n","/**\n * PCM16 playback for `voice.monitor_audio` events on the operator WebSocket.\n * Adapted from the main app's use-monitor-audio-player.ts.\n */\n\nimport type { ChannelEvent } from '../realtime/types';\nimport { MONITOR_SAMPLE_RATE } from './pcm';\n\ntype MonitorAudioRole = 'user' | 'assistant';\n\ninterface MonitorAudioEvent {\n type: 'CUSTOM';\n name: 'voice.monitor_audio';\n value: {\n role: MonitorAudioRole;\n format: 'pcm16';\n sampleRate: number;\n data: string;\n };\n}\n\nfunction isMonitorAudioEvent(event: ChannelEvent): event is MonitorAudioEvent & ChannelEvent {\n return event.type === 'CUSTOM' && event.name === 'voice.monitor_audio';\n}\n\nfunction isInterruptEvent(event: ChannelEvent): boolean {\n return event.type === 'CUSTOM' && event.name === 'voice.interrupt';\n}\n\nfunction isCallEndEvent(event: ChannelEvent): boolean {\n return event.type === 'CUSTOM' && event.name === 'voice.call_end';\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nfunction decodePcm16IntoChannel(bytes: Uint8Array, channel: Float32Array): void {\n const evenLen = bytes.length - (bytes.length % 2);\n const samples = evenLen / 2;\n const view = new DataView(bytes.buffer, bytes.byteOffset, evenLen);\n for (let i = 0; i < samples; i += 1) {\n channel[i] = view.getInt16(i * 2, true) / 32768;\n }\n}\n\nexport interface MonitorAudioPlayer {\n prepare: () => void;\n handleEvent: (event: ChannelEvent) => void;\n dispose: () => void;\n isPlaying: () => boolean;\n}\n\nexport function createMonitorAudioPlayer(): MonitorAudioPlayer {\n let audioContext: AudioContext | null = null;\n const nextStartTime: Record<MonitorAudioRole, number> = { user: 0, assistant: 0 };\n let playing = false;\n const assistantSources: AudioBufferSourceNode[] = [];\n\n const ensureContext = (): AudioContext | null => {\n if (typeof window === 'undefined') return null;\n if (!audioContext) {\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return null;\n audioContext = new Ctx({ sampleRate: MONITOR_SAMPLE_RATE });\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n }\n if (audioContext.state === 'suspended') {\n void audioContext.resume();\n }\n return audioContext;\n };\n\n const prepare = (): void => {\n ensureContext();\n };\n\n const handleEvent = (event: ChannelEvent): void => {\n if (isInterruptEvent(event)) {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n nextStartTime.assistant = 0;\n return;\n }\n\n if (isCallEndEvent(event)) {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n playing = false;\n return;\n }\n\n if (!isMonitorAudioEvent(event)) return;\n\n const { data, sampleRate, format, role } = event.value;\n if (format !== 'pcm16') return;\n if (role !== 'user' && role !== 'assistant') return;\n\n const ctx = ensureContext();\n if (!ctx) return;\n\n const bytes = base64ToUint8Array(data);\n const sampleCount = Math.floor(bytes.length / 2);\n if (sampleCount === 0) return;\n\n const buffer = ctx.createBuffer(1, sampleCount, sampleRate || MONITOR_SAMPLE_RATE);\n decodePcm16IntoChannel(bytes, buffer.getChannelData(0));\n\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.connect(ctx.destination);\n\n const now = ctx.currentTime;\n const cursor = nextStartTime[role];\n const startAt = cursor < now + 0.02 ? now + 0.02 : cursor;\n source.start(startAt);\n nextStartTime[role] = startAt + buffer.duration;\n playing = true;\n\n if (role === 'assistant') {\n assistantSources.push(source);\n source.onended = () => {\n const index = assistantSources.indexOf(source);\n if (index >= 0) assistantSources.splice(index, 1);\n };\n }\n };\n\n const dispose = (): void => {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n const ctx = audioContext;\n audioContext = null;\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n playing = false;\n if (ctx && ctx.state !== 'closed') {\n void ctx.close().catch(() => {\n /* already closed */\n });\n }\n };\n\n return {\n prepare,\n handleEvent,\n dispose,\n isPlaying: () => playing,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/audio.d.cts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { C as ChannelEvent } from './types-DkYyrvoC.cjs';
|
|
2
|
+
|
|
3
|
+
/** PCM16 helpers aligned with main-app web voice (24 kHz). */
|
|
4
|
+
declare const SERVER_SAMPLE_RATE = 24000;
|
|
5
|
+
declare const TARGET_SAMPLE_RATE = 24000;
|
|
6
|
+
declare const MONITOR_SAMPLE_RATE = 16000;
|
|
7
|
+
declare function float32ArrayToBase64(float32Array: Float32Array): string;
|
|
8
|
+
declare function base64ToFloat32Array(base64: string): Float32Array;
|
|
9
|
+
declare function concatFloat32Arrays(arrays: Float32Array[]): Float32Array;
|
|
10
|
+
declare function downsampleAudio(audioData: Float32Array, originalSampleRate: number, targetSampleRate: number): Float32Array;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* PCM16 playback for `voice.monitor_audio` events on the operator WebSocket.
|
|
14
|
+
* Adapted from the main app's use-monitor-audio-player.ts.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MonitorAudioPlayer {
|
|
18
|
+
prepare: () => void;
|
|
19
|
+
handleEvent: (event: ChannelEvent) => void;
|
|
20
|
+
dispose: () => void;
|
|
21
|
+
isPlaying: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
declare function createMonitorAudioPlayer(): MonitorAudioPlayer;
|
|
24
|
+
|
|
25
|
+
export { MONITOR_SAMPLE_RATE, type MonitorAudioPlayer, SERVER_SAMPLE_RATE, TARGET_SAMPLE_RATE, base64ToFloat32Array, concatFloat32Arrays, createMonitorAudioPlayer, downsampleAudio, float32ArrayToBase64 };
|
package/dist/audio.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { C as ChannelEvent } from './types-DkYyrvoC.js';
|
|
2
|
+
|
|
3
|
+
/** PCM16 helpers aligned with main-app web voice (24 kHz). */
|
|
4
|
+
declare const SERVER_SAMPLE_RATE = 24000;
|
|
5
|
+
declare const TARGET_SAMPLE_RATE = 24000;
|
|
6
|
+
declare const MONITOR_SAMPLE_RATE = 16000;
|
|
7
|
+
declare function float32ArrayToBase64(float32Array: Float32Array): string;
|
|
8
|
+
declare function base64ToFloat32Array(base64: string): Float32Array;
|
|
9
|
+
declare function concatFloat32Arrays(arrays: Float32Array[]): Float32Array;
|
|
10
|
+
declare function downsampleAudio(audioData: Float32Array, originalSampleRate: number, targetSampleRate: number): Float32Array;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* PCM16 playback for `voice.monitor_audio` events on the operator WebSocket.
|
|
14
|
+
* Adapted from the main app's use-monitor-audio-player.ts.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface MonitorAudioPlayer {
|
|
18
|
+
prepare: () => void;
|
|
19
|
+
handleEvent: (event: ChannelEvent) => void;
|
|
20
|
+
dispose: () => void;
|
|
21
|
+
isPlaying: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
declare function createMonitorAudioPlayer(): MonitorAudioPlayer;
|
|
24
|
+
|
|
25
|
+
export { MONITOR_SAMPLE_RATE, type MonitorAudioPlayer, SERVER_SAMPLE_RATE, TARGET_SAMPLE_RATE, base64ToFloat32Array, concatFloat32Arrays, createMonitorAudioPlayer, downsampleAudio, float32ArrayToBase64 };
|
package/dist/audio.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MONITOR_SAMPLE_RATE,
|
|
3
|
+
SERVER_SAMPLE_RATE,
|
|
4
|
+
TARGET_SAMPLE_RATE,
|
|
5
|
+
base64ToFloat32Array,
|
|
6
|
+
concatFloat32Arrays,
|
|
7
|
+
downsampleAudio,
|
|
8
|
+
float32ArrayToBase64
|
|
9
|
+
} from "./chunk-BSOOMHW6.js";
|
|
10
|
+
|
|
11
|
+
// src/audio/monitor-player.ts
|
|
12
|
+
function isMonitorAudioEvent(event) {
|
|
13
|
+
return event.type === "CUSTOM" && event.name === "voice.monitor_audio";
|
|
14
|
+
}
|
|
15
|
+
function isInterruptEvent(event) {
|
|
16
|
+
return event.type === "CUSTOM" && event.name === "voice.interrupt";
|
|
17
|
+
}
|
|
18
|
+
function isCallEndEvent(event) {
|
|
19
|
+
return event.type === "CUSTOM" && event.name === "voice.call_end";
|
|
20
|
+
}
|
|
21
|
+
function base64ToUint8Array(base64) {
|
|
22
|
+
const binary = atob(base64);
|
|
23
|
+
const bytes = new Uint8Array(binary.length);
|
|
24
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
25
|
+
bytes[i] = binary.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
function decodePcm16IntoChannel(bytes, channel) {
|
|
30
|
+
const evenLen = bytes.length - bytes.length % 2;
|
|
31
|
+
const samples = evenLen / 2;
|
|
32
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, evenLen);
|
|
33
|
+
for (let i = 0; i < samples; i += 1) {
|
|
34
|
+
channel[i] = view.getInt16(i * 2, true) / 32768;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function createMonitorAudioPlayer() {
|
|
38
|
+
let audioContext = null;
|
|
39
|
+
const nextStartTime = { user: 0, assistant: 0 };
|
|
40
|
+
let playing = false;
|
|
41
|
+
const assistantSources = [];
|
|
42
|
+
const ensureContext = () => {
|
|
43
|
+
if (typeof window === "undefined") return null;
|
|
44
|
+
if (!audioContext) {
|
|
45
|
+
const Ctx = window.AudioContext ?? window.webkitAudioContext;
|
|
46
|
+
if (!Ctx) return null;
|
|
47
|
+
audioContext = new Ctx({ sampleRate: MONITOR_SAMPLE_RATE });
|
|
48
|
+
nextStartTime.user = 0;
|
|
49
|
+
nextStartTime.assistant = 0;
|
|
50
|
+
}
|
|
51
|
+
if (audioContext.state === "suspended") {
|
|
52
|
+
void audioContext.resume();
|
|
53
|
+
}
|
|
54
|
+
return audioContext;
|
|
55
|
+
};
|
|
56
|
+
const prepare = () => {
|
|
57
|
+
ensureContext();
|
|
58
|
+
};
|
|
59
|
+
const handleEvent = (event) => {
|
|
60
|
+
if (isInterruptEvent(event)) {
|
|
61
|
+
for (const src of assistantSources) {
|
|
62
|
+
try {
|
|
63
|
+
src.stop();
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
assistantSources.length = 0;
|
|
68
|
+
nextStartTime.assistant = 0;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (isCallEndEvent(event)) {
|
|
72
|
+
for (const src of assistantSources) {
|
|
73
|
+
try {
|
|
74
|
+
src.stop();
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
assistantSources.length = 0;
|
|
79
|
+
nextStartTime.user = 0;
|
|
80
|
+
nextStartTime.assistant = 0;
|
|
81
|
+
playing = false;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!isMonitorAudioEvent(event)) return;
|
|
85
|
+
const { data, sampleRate, format, role } = event.value;
|
|
86
|
+
if (format !== "pcm16") return;
|
|
87
|
+
if (role !== "user" && role !== "assistant") return;
|
|
88
|
+
const ctx = ensureContext();
|
|
89
|
+
if (!ctx) return;
|
|
90
|
+
const bytes = base64ToUint8Array(data);
|
|
91
|
+
const sampleCount = Math.floor(bytes.length / 2);
|
|
92
|
+
if (sampleCount === 0) return;
|
|
93
|
+
const buffer = ctx.createBuffer(1, sampleCount, sampleRate || MONITOR_SAMPLE_RATE);
|
|
94
|
+
decodePcm16IntoChannel(bytes, buffer.getChannelData(0));
|
|
95
|
+
const source = ctx.createBufferSource();
|
|
96
|
+
source.buffer = buffer;
|
|
97
|
+
source.connect(ctx.destination);
|
|
98
|
+
const now = ctx.currentTime;
|
|
99
|
+
const cursor = nextStartTime[role];
|
|
100
|
+
const startAt = cursor < now + 0.02 ? now + 0.02 : cursor;
|
|
101
|
+
source.start(startAt);
|
|
102
|
+
nextStartTime[role] = startAt + buffer.duration;
|
|
103
|
+
playing = true;
|
|
104
|
+
if (role === "assistant") {
|
|
105
|
+
assistantSources.push(source);
|
|
106
|
+
source.onended = () => {
|
|
107
|
+
const index = assistantSources.indexOf(source);
|
|
108
|
+
if (index >= 0) assistantSources.splice(index, 1);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const dispose = () => {
|
|
113
|
+
for (const src of assistantSources) {
|
|
114
|
+
try {
|
|
115
|
+
src.stop();
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
assistantSources.length = 0;
|
|
120
|
+
const ctx = audioContext;
|
|
121
|
+
audioContext = null;
|
|
122
|
+
nextStartTime.user = 0;
|
|
123
|
+
nextStartTime.assistant = 0;
|
|
124
|
+
playing = false;
|
|
125
|
+
if (ctx && ctx.state !== "closed") {
|
|
126
|
+
void ctx.close().catch(() => {
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
prepare,
|
|
132
|
+
handleEvent,
|
|
133
|
+
dispose,
|
|
134
|
+
isPlaying: () => playing
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
MONITOR_SAMPLE_RATE,
|
|
139
|
+
SERVER_SAMPLE_RATE,
|
|
140
|
+
TARGET_SAMPLE_RATE,
|
|
141
|
+
base64ToFloat32Array,
|
|
142
|
+
concatFloat32Arrays,
|
|
143
|
+
createMonitorAudioPlayer,
|
|
144
|
+
downsampleAudio,
|
|
145
|
+
float32ArrayToBase64
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audio/monitor-player.ts"],"sourcesContent":["/**\n * PCM16 playback for `voice.monitor_audio` events on the operator WebSocket.\n * Adapted from the main app's use-monitor-audio-player.ts.\n */\n\nimport type { ChannelEvent } from '../realtime/types';\nimport { MONITOR_SAMPLE_RATE } from './pcm';\n\ntype MonitorAudioRole = 'user' | 'assistant';\n\ninterface MonitorAudioEvent {\n type: 'CUSTOM';\n name: 'voice.monitor_audio';\n value: {\n role: MonitorAudioRole;\n format: 'pcm16';\n sampleRate: number;\n data: string;\n };\n}\n\nfunction isMonitorAudioEvent(event: ChannelEvent): event is MonitorAudioEvent & ChannelEvent {\n return event.type === 'CUSTOM' && event.name === 'voice.monitor_audio';\n}\n\nfunction isInterruptEvent(event: ChannelEvent): boolean {\n return event.type === 'CUSTOM' && event.name === 'voice.interrupt';\n}\n\nfunction isCallEndEvent(event: ChannelEvent): boolean {\n return event.type === 'CUSTOM' && event.name === 'voice.call_end';\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nfunction decodePcm16IntoChannel(bytes: Uint8Array, channel: Float32Array): void {\n const evenLen = bytes.length - (bytes.length % 2);\n const samples = evenLen / 2;\n const view = new DataView(bytes.buffer, bytes.byteOffset, evenLen);\n for (let i = 0; i < samples; i += 1) {\n channel[i] = view.getInt16(i * 2, true) / 32768;\n }\n}\n\nexport interface MonitorAudioPlayer {\n prepare: () => void;\n handleEvent: (event: ChannelEvent) => void;\n dispose: () => void;\n isPlaying: () => boolean;\n}\n\nexport function createMonitorAudioPlayer(): MonitorAudioPlayer {\n let audioContext: AudioContext | null = null;\n const nextStartTime: Record<MonitorAudioRole, number> = { user: 0, assistant: 0 };\n let playing = false;\n const assistantSources: AudioBufferSourceNode[] = [];\n\n const ensureContext = (): AudioContext | null => {\n if (typeof window === 'undefined') return null;\n if (!audioContext) {\n const Ctx =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return null;\n audioContext = new Ctx({ sampleRate: MONITOR_SAMPLE_RATE });\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n }\n if (audioContext.state === 'suspended') {\n void audioContext.resume();\n }\n return audioContext;\n };\n\n const prepare = (): void => {\n ensureContext();\n };\n\n const handleEvent = (event: ChannelEvent): void => {\n if (isInterruptEvent(event)) {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n nextStartTime.assistant = 0;\n return;\n }\n\n if (isCallEndEvent(event)) {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n playing = false;\n return;\n }\n\n if (!isMonitorAudioEvent(event)) return;\n\n const { data, sampleRate, format, role } = event.value;\n if (format !== 'pcm16') return;\n if (role !== 'user' && role !== 'assistant') return;\n\n const ctx = ensureContext();\n if (!ctx) return;\n\n const bytes = base64ToUint8Array(data);\n const sampleCount = Math.floor(bytes.length / 2);\n if (sampleCount === 0) return;\n\n const buffer = ctx.createBuffer(1, sampleCount, sampleRate || MONITOR_SAMPLE_RATE);\n decodePcm16IntoChannel(bytes, buffer.getChannelData(0));\n\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.connect(ctx.destination);\n\n const now = ctx.currentTime;\n const cursor = nextStartTime[role];\n const startAt = cursor < now + 0.02 ? now + 0.02 : cursor;\n source.start(startAt);\n nextStartTime[role] = startAt + buffer.duration;\n playing = true;\n\n if (role === 'assistant') {\n assistantSources.push(source);\n source.onended = () => {\n const index = assistantSources.indexOf(source);\n if (index >= 0) assistantSources.splice(index, 1);\n };\n }\n };\n\n const dispose = (): void => {\n for (const src of assistantSources) {\n try {\n src.stop();\n } catch {\n /* already stopped */\n }\n }\n assistantSources.length = 0;\n const ctx = audioContext;\n audioContext = null;\n nextStartTime.user = 0;\n nextStartTime.assistant = 0;\n playing = false;\n if (ctx && ctx.state !== 'closed') {\n void ctx.close().catch(() => {\n /* already closed */\n });\n }\n };\n\n return {\n prepare,\n handleEvent,\n dispose,\n isPlaying: () => playing,\n };\n}\n"],"mappings":"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","names":[]}
|