@myned-ai/avatar-chat-widget 0.10.0 → 0.12.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.
|
@@ -137,6 +137,11 @@ declare class AvatarChatElement extends HTMLElement {
|
|
|
137
137
|
* Send message programmatically
|
|
138
138
|
*/
|
|
139
139
|
sendMessage(text: string): void;
|
|
140
|
+
/**
|
|
141
|
+
* Expose triggerAction for client-side Actions debugging
|
|
142
|
+
* Dispatches a custom nyxAction event as if the server triggered it
|
|
143
|
+
*/
|
|
144
|
+
triggerAction(function_name: string, args?: Record<string, any>): void;
|
|
140
145
|
/**
|
|
141
146
|
* Check if mounted
|
|
142
147
|
*/
|
|
@@ -546,6 +546,7 @@ class AvatarProtocolClient extends EventEmitter {
|
|
|
546
546
|
this.socket.on("transcript_done", (msg) => this.handleTranscriptDone(msg));
|
|
547
547
|
this.socket.on("interrupt", (msg) => this.handleInterrupt(msg));
|
|
548
548
|
this.socket.on("avatar_state", (msg) => this.emit("avatar_state", msg));
|
|
549
|
+
this.socket.on("trigger_action", (msg) => this.emit("trigger_action", msg));
|
|
549
550
|
this.socket.on("pong", (msg) => log$h.debug("Pong received", msg));
|
|
550
551
|
}
|
|
551
552
|
// ------------------------------------------------------------------
|
|
@@ -1789,7 +1790,15 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1789
1790
|
return _AudioContextManagerImpl._instance;
|
|
1790
1791
|
}
|
|
1791
1792
|
/**
|
|
1792
|
-
*
|
|
1793
|
+
* Set the sample rate for when the AudioContext is eventually created.
|
|
1794
|
+
* Call this early (e.g., from server config) before any audio session starts.
|
|
1795
|
+
*/
|
|
1796
|
+
setSampleRate(sampleRate) {
|
|
1797
|
+
this._sampleRate = sampleRate;
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Get the shared AudioContext. Creates lazily if needed.
|
|
1801
|
+
* Prefer ensureAudioReady() from user-gesture handlers to guarantee iOS unlock.
|
|
1793
1802
|
* @param sampleRate Optional sample rate (only used on first creation)
|
|
1794
1803
|
*/
|
|
1795
1804
|
getContext(sampleRate) {
|
|
@@ -1799,6 +1808,27 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1799
1808
|
if (sampleRate) {
|
|
1800
1809
|
this._sampleRate = sampleRate;
|
|
1801
1810
|
}
|
|
1811
|
+
return this.createContext();
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Single idempotent entry point: create (if needed) + resume AudioContext.
|
|
1815
|
+
* Call this synchronously from any user-gesture handler (pointerdown, touchend,
|
|
1816
|
+
* click, keydown) to guarantee iOS audio unlock.
|
|
1817
|
+
*/
|
|
1818
|
+
ensureAudioReady() {
|
|
1819
|
+
if (!this._context) {
|
|
1820
|
+
this.createContext();
|
|
1821
|
+
}
|
|
1822
|
+
if (this._context && this._context.state !== "running") {
|
|
1823
|
+
this.resume().catch((error2) => {
|
|
1824
|
+
log$c.warn("ensureAudioReady resume failed (will retry on next gesture):", error2);
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Internal: create the AudioContext and wire up fallback document listeners.
|
|
1830
|
+
*/
|
|
1831
|
+
createContext() {
|
|
1802
1832
|
try {
|
|
1803
1833
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
1804
1834
|
if (!AudioContextClass) {
|
|
@@ -1807,12 +1837,14 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1807
1837
|
this._context = new AudioContextClass({
|
|
1808
1838
|
sampleRate: this._sampleRate,
|
|
1809
1839
|
latencyHint: "interactive"
|
|
1810
|
-
// Optimize for real-time
|
|
1811
1840
|
});
|
|
1812
1841
|
log$c.info(`AudioContext created: sampleRate=${this._context.sampleRate}, state=${this._context.state}`);
|
|
1813
1842
|
this.setupResumeListener();
|
|
1814
1843
|
this._context.onstatechange = () => {
|
|
1815
1844
|
log$c.debug(`AudioContext state changed: ${this._context?.state}`);
|
|
1845
|
+
if (this._context?.state === "running") {
|
|
1846
|
+
this.removeResumeListeners();
|
|
1847
|
+
}
|
|
1816
1848
|
};
|
|
1817
1849
|
} catch (error2) {
|
|
1818
1850
|
errorBoundary.handleError(error2, "audio-context-manager");
|
|
@@ -1821,26 +1853,29 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1821
1853
|
return this._context;
|
|
1822
1854
|
}
|
|
1823
1855
|
/**
|
|
1824
|
-
*
|
|
1825
|
-
*
|
|
1856
|
+
* Fallback document-level listeners for audio unlock.
|
|
1857
|
+
* These are a safety net — primary unlock happens via ensureAudioReady()
|
|
1858
|
+
* called from widget gesture handlers.
|
|
1826
1859
|
*/
|
|
1827
1860
|
setupResumeListener() {
|
|
1828
1861
|
if (this._isResumeListenerAdded) {
|
|
1829
1862
|
return;
|
|
1830
1863
|
}
|
|
1831
|
-
this._listenerEvents = ["
|
|
1832
|
-
this._interactionHandler =
|
|
1833
|
-
this.
|
|
1834
|
-
|
|
1864
|
+
this._listenerEvents = ["pointerdown", "touchend", "keydown"];
|
|
1865
|
+
this._interactionHandler = () => {
|
|
1866
|
+
if (this._context && this._context.state !== "running") {
|
|
1867
|
+
this._context.resume().catch(() => {
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1835
1870
|
};
|
|
1836
1871
|
this._listenerEvents.forEach((event) => {
|
|
1837
|
-
document.addEventListener(event, this._interactionHandler, {
|
|
1872
|
+
document.addEventListener(event, this._interactionHandler, { passive: true });
|
|
1838
1873
|
});
|
|
1839
1874
|
this._isResumeListenerAdded = true;
|
|
1840
|
-
log$c.debug("Audio resume listeners added");
|
|
1875
|
+
log$c.debug("Audio resume fallback listeners added");
|
|
1841
1876
|
}
|
|
1842
1877
|
/**
|
|
1843
|
-
* Remove resume listeners (
|
|
1878
|
+
* Remove resume listeners (only after context is running, or on cleanup)
|
|
1844
1879
|
*/
|
|
1845
1880
|
removeResumeListeners() {
|
|
1846
1881
|
if (this._interactionHandler) {
|
|
@@ -1849,6 +1884,8 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1849
1884
|
});
|
|
1850
1885
|
this._interactionHandler = null;
|
|
1851
1886
|
this._listenerEvents = [];
|
|
1887
|
+
this._isResumeListenerAdded = false;
|
|
1888
|
+
log$c.debug("Audio resume fallback listeners removed");
|
|
1852
1889
|
}
|
|
1853
1890
|
}
|
|
1854
1891
|
/**
|
|
@@ -1869,6 +1906,7 @@ const _AudioContextManagerImpl = class _AudioContextManagerImpl {
|
|
|
1869
1906
|
try {
|
|
1870
1907
|
await this._context.resume();
|
|
1871
1908
|
log$c.info("AudioContext resumed successfully");
|
|
1909
|
+
this.removeResumeListeners();
|
|
1872
1910
|
} catch (error2) {
|
|
1873
1911
|
log$c.error("Failed to resume AudioContext:", error2);
|
|
1874
1912
|
errorBoundary.handleError(error2, "audio-context-manager");
|
|
@@ -1997,7 +2035,7 @@ class AudioOutput {
|
|
|
1997
2035
|
this.chunkDurationMs = 100;
|
|
1998
2036
|
this.audioBuffer = new CircularBuffer(CONFIG.audio.output.maxBufferFrames);
|
|
1999
2037
|
this.adaptiveBuffer = new AdaptiveBuffer(100, 50, 500);
|
|
2000
|
-
AudioContextManager.
|
|
2038
|
+
AudioContextManager.setSampleRate(this.sampleRate);
|
|
2001
2039
|
}
|
|
2002
2040
|
/**
|
|
2003
2041
|
* Get the shared AudioContext
|
|
@@ -2010,6 +2048,7 @@ class AudioOutput {
|
|
|
2010
2048
|
*/
|
|
2011
2049
|
setDefaultSampleRate(sampleRate) {
|
|
2012
2050
|
this.sampleRate = sampleRate;
|
|
2051
|
+
AudioContextManager.setSampleRate(sampleRate);
|
|
2013
2052
|
log$b.info(`Output sample rate set to ${sampleRate}Hz`);
|
|
2014
2053
|
}
|
|
2015
2054
|
startSession(sessionId, sampleRate) {
|
|
@@ -2023,6 +2062,7 @@ class AudioOutput {
|
|
|
2023
2062
|
this.isPlaying = false;
|
|
2024
2063
|
this.nextPlayTime = 0;
|
|
2025
2064
|
AudioContextManager.resume().catch(() => {
|
|
2065
|
+
log$b.warn("AudioContext resume failed in startSession — will retry on next user gesture");
|
|
2026
2066
|
});
|
|
2027
2067
|
}
|
|
2028
2068
|
addAudioChunk(data, timestamp) {
|
|
@@ -2534,7 +2574,7 @@ class SyncPlayback {
|
|
|
2534
2574
|
this.floatBuffer = new Float32Array(this.maxSamplesPerFrame);
|
|
2535
2575
|
this.visibilityHandler = this.handleVisibilityChange.bind(this);
|
|
2536
2576
|
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
2537
|
-
AudioContextManager.
|
|
2577
|
+
AudioContextManager.setSampleRate(this.sampleRate);
|
|
2538
2578
|
}
|
|
2539
2579
|
/**
|
|
2540
2580
|
* Get the shared AudioContext
|
|
@@ -2571,6 +2611,7 @@ class SyncPlayback {
|
|
|
2571
2611
|
*/
|
|
2572
2612
|
setDefaultSampleRate(sampleRate) {
|
|
2573
2613
|
this.sampleRate = sampleRate;
|
|
2614
|
+
AudioContextManager.setSampleRate(sampleRate);
|
|
2574
2615
|
log$8.info(`SyncPlayback sample rate set to ${sampleRate}Hz`);
|
|
2575
2616
|
}
|
|
2576
2617
|
startSession(sessionId, sampleRate) {
|
|
@@ -2584,6 +2625,7 @@ class SyncPlayback {
|
|
|
2584
2625
|
this.sampleRate = sampleRate;
|
|
2585
2626
|
}
|
|
2586
2627
|
AudioContextManager.resume().catch(() => {
|
|
2628
|
+
log$8.warn("AudioContext resume failed in startSession — will retry on next user gesture");
|
|
2587
2629
|
});
|
|
2588
2630
|
try {
|
|
2589
2631
|
const ctx = this.audioContext;
|
|
@@ -3922,11 +3964,19 @@ class ChatManager {
|
|
|
3922
3964
|
this.protocolClient.on("interrupt", (event) => {
|
|
3923
3965
|
this.handleInterrupt(event);
|
|
3924
3966
|
});
|
|
3967
|
+
this.protocolClient.on("trigger_action", (event) => {
|
|
3968
|
+
this.handleTriggerAction(event);
|
|
3969
|
+
});
|
|
3925
3970
|
this.protocolClient.on("error", (err) => log$4.error("Protocol Error:", err));
|
|
3926
3971
|
}
|
|
3927
3972
|
// ============================================================================
|
|
3928
3973
|
// Protocol Handlers
|
|
3929
3974
|
// ============================================================================
|
|
3975
|
+
handleTriggerAction(event) {
|
|
3976
|
+
log$4.info(`🎯 Action Triggered: ${event.function_name}`, event.arguments);
|
|
3977
|
+
const customEvent = new CustomEvent("nyxAction", { detail: event });
|
|
3978
|
+
window.dispatchEvent(customEvent);
|
|
3979
|
+
}
|
|
3930
3980
|
handleAudioStart(event) {
|
|
3931
3981
|
this.setTyping(false);
|
|
3932
3982
|
if (this.syncFramesBeforeStart > 0) {
|
|
@@ -5875,6 +5925,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5875
5925
|
}
|
|
5876
5926
|
});
|
|
5877
5927
|
chatInput.addEventListener("focus", () => {
|
|
5928
|
+
AudioContextManager.ensureAudioReady();
|
|
5878
5929
|
const root = this.shadow.querySelector(".widget-root");
|
|
5879
5930
|
if (root) root.classList.add("input-focused");
|
|
5880
5931
|
});
|
|
@@ -5905,6 +5956,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5905
5956
|
const handleChipClick = (e) => {
|
|
5906
5957
|
const target = e.target;
|
|
5907
5958
|
if (target.classList.contains("suggestion-chip")) {
|
|
5959
|
+
AudioContextManager.ensureAudioReady();
|
|
5908
5960
|
this.markHasMessages();
|
|
5909
5961
|
hideSuggestions();
|
|
5910
5962
|
const text = target.textContent;
|
|
@@ -5918,11 +5970,13 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5918
5970
|
quickReplies?.addEventListener("click", handleChipClick);
|
|
5919
5971
|
avatarSuggestions?.addEventListener("click", handleChipClick);
|
|
5920
5972
|
micBtn?.addEventListener("click", () => {
|
|
5973
|
+
AudioContextManager.ensureAudioReady();
|
|
5921
5974
|
this.markHasMessages();
|
|
5922
5975
|
hideSuggestions();
|
|
5923
5976
|
});
|
|
5924
5977
|
chatInput.addEventListener("keydown", (e) => {
|
|
5925
5978
|
if (e.key === "Enter") {
|
|
5979
|
+
AudioContextManager.ensureAudioReady();
|
|
5926
5980
|
this.markHasMessages();
|
|
5927
5981
|
hideSuggestions();
|
|
5928
5982
|
setTimeout(() => {
|
|
@@ -6124,6 +6178,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
6124
6178
|
*/
|
|
6125
6179
|
async expand() {
|
|
6126
6180
|
if (!this._isCollapsed) return;
|
|
6181
|
+
AudioContextManager.ensureAudioReady();
|
|
6127
6182
|
this._isCollapsed = false;
|
|
6128
6183
|
this.classList.remove("collapsed");
|
|
6129
6184
|
this.style.width = `${this.config.width}px`;
|
|
@@ -6160,6 +6215,20 @@ class AvatarChatElement extends HTMLElement {
|
|
|
6160
6215
|
this.chatManager.sendText(text);
|
|
6161
6216
|
}
|
|
6162
6217
|
}
|
|
6218
|
+
/**
|
|
6219
|
+
* Expose triggerAction for client-side Actions debugging
|
|
6220
|
+
* Dispatches a custom nyxAction event as if the server triggered it
|
|
6221
|
+
*/
|
|
6222
|
+
triggerAction(function_name, args = {}) {
|
|
6223
|
+
const eventDetail = {
|
|
6224
|
+
type: "trigger_action",
|
|
6225
|
+
function_name,
|
|
6226
|
+
arguments: args
|
|
6227
|
+
};
|
|
6228
|
+
log$2.info(`🛠️ Debug Action Triggered manually: ${function_name}`, args);
|
|
6229
|
+
const customEvent = new CustomEvent("nyxAction", { detail: eventDetail });
|
|
6230
|
+
window.dispatchEvent(customEvent);
|
|
6231
|
+
}
|
|
6163
6232
|
/**
|
|
6164
6233
|
* Check if mounted
|
|
6165
6234
|
*/
|
|
@@ -6371,7 +6440,8 @@ const AvatarChat = {
|
|
|
6371
6440
|
collapse: () => widget.collapse(),
|
|
6372
6441
|
isMounted: () => widget.isMounted(),
|
|
6373
6442
|
isConnected: () => widget.isServerConnected(),
|
|
6374
|
-
reconnect: () => widget.reconnect()
|
|
6443
|
+
reconnect: () => widget.reconnect(),
|
|
6444
|
+
triggerAction: (name, args) => widget.triggerAction(name, args)
|
|
6375
6445
|
};
|
|
6376
6446
|
},
|
|
6377
6447
|
/**
|
|
@@ -6402,7 +6472,8 @@ const AvatarChat = {
|
|
|
6402
6472
|
collapse: () => widget.collapse(),
|
|
6403
6473
|
isMounted: () => widget.isMounted(),
|
|
6404
6474
|
isConnected: () => widget.isServerConnected(),
|
|
6405
|
-
reconnect: () => widget.reconnect()
|
|
6475
|
+
reconnect: () => widget.reconnect(),
|
|
6476
|
+
triggerAction: (name, args) => widget.triggerAction(name, args)
|
|
6406
6477
|
};
|
|
6407
6478
|
}
|
|
6408
6479
|
};
|