@bytexbyte/nxtlinq-ai-agent-web-development 0.1.7 → 0.1.8

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.
@@ -12,6 +12,8 @@ export type UseNxtlinqAgentResult = NxtlinqAgentSnapshot & {
12
12
  setMessages: (messages: Message[]) => void;
13
13
  postTextTts: (text: string) => Promise<PostTextTtsResult>;
14
14
  buildTextTtsPlaybackUri: (result: PostTextTtsResult) => string;
15
+ /** Stream PCM16 TTS using browser fetch streaming. Web-only. */
16
+ streamTextTts: (text: string, onChunk: (pcm16: Int16Array) => void, signal?: AbortSignal) => Promise<void>;
15
17
  };
16
18
  /** Subscribe to {@link NxtlinqAgent} state for custom Web chat UIs. */
17
19
  export declare function useNxtlinqAgent(): UseNxtlinqAgentResult;
@@ -1 +1 @@
1
- {"version":3,"file":"useNxtlinqAgent.d.ts","sourceRoot":"","sources":["../../src/hooks/useNxtlinqAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAExE,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,KAAK,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;IACjD,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,KACvC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,oBAAoB,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1D,uBAAuB,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,MAAM,CAAC;CAChE,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,qBAAqB,CAiDvD"}
1
+ {"version":3,"file":"useNxtlinqAgent.d.ts","sourceRoot":"","sources":["../../src/hooks/useNxtlinqAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAExE,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,KAAK,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;IACjD,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,KACvC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,oBAAoB,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1D,uBAAuB,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,MAAM,CAAC;IAC/D,gEAAgE;IAChE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5G,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,qBAAqB,CAwDvD"}
@@ -10,6 +10,7 @@ export function useNxtlinqAgent() {
10
10
  const setMessages = useCallback((messages) => agent.setMessages(messages), [agent]);
11
11
  const postTextTts = useCallback((text) => agent.postTextTts(text), [agent]);
12
12
  const buildTextTtsPlaybackUri = useCallback((result) => agent.buildTextTtsPlaybackUri(result), [agent]);
13
+ const streamTextTts = useCallback((text, onChunk, signal) => agent.streamTextTts(text, onChunk, signal), [agent]);
13
14
  return {
14
15
  agent,
15
16
  ...snapshot,
@@ -19,5 +20,6 @@ export function useNxtlinqAgent() {
19
20
  setMessages,
20
21
  postTextTts,
21
22
  buildTextTtsPlaybackUri,
23
+ streamTextTts,
22
24
  };
23
25
  }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,6 @@ export { WEB_AGENT_DEFAULTS, applyWebAgentDefaults } from './webAgentDefaults';
7
7
  export { fileToAttachment } from './utils/fileToAttachment';
8
8
  export type { Message, Attachment, AgentResponse, SendMessageOptions, NxtlinqAgentSnapshot, ToolUse, StartVoiceSessionOptions, VoiceSession, VoiceStatus, VoiceTranscriptEvent, VoiceDoneEvent, VoiceUserInputOptions, VoiceGreetingOptions, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
9
9
  export { NxtlinqAgent, setApiHosts, createBrowserWebRTCPort, createBrowserStoragePort, createDefaultHttpPort, VoiceNotSupportedError, mapServerHistoryToMessages, appendServerHistoryIntoMessages, STORAGE_KEYS, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
10
- export { createNxtlinqApi, getAiAgentApiHost, startVoiceSession, createVoiceApi, useDraggable, useLocalStorage, useSessionStorage, useResizable, synthesizeSpeechToBuffer, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, connectWallet, disconnectWallet, validateToken, createAITMetadata, generateAITId, prepareAITCreation, createNotification, getNotificationIcon, containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './legacy';
10
+ export { createNxtlinqApi, getAiAgentApiHost, startVoiceSession, createVoiceApi, useDraggable, useLocalStorage, useSessionStorage, useResizable, synthesizeSpeechToBuffer, streamSpeechToAudioContext, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, connectWallet, disconnectWallet, validateToken, createAITMetadata, generateAITId, prepareAITCreation, createNotification, getNotificationIcon, containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './legacy';
11
11
  export type { ResizeCorner } from './legacy';
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,WAAW,GACjB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,YAAY,EACV,OAAO,EACP,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,OAAO,EACP,wBAAwB,EACxB,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,YAAY,GACb,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,KAAK,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,WAAW,GACjB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,YAAY,EACV,OAAO,EACP,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,OAAO,EACP,wBAAwB,EACxB,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,YAAY,GACb,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,KAAK,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -6,4 +6,4 @@ export { createWebPlatformPorts, } from './ports/createWebPlatformPorts';
6
6
  export { WEB_AGENT_DEFAULTS, applyWebAgentDefaults } from './webAgentDefaults';
7
7
  export { fileToAttachment } from './utils/fileToAttachment';
8
8
  export { NxtlinqAgent, setApiHosts, createBrowserWebRTCPort, createBrowserStoragePort, createDefaultHttpPort, VoiceNotSupportedError, mapServerHistoryToMessages, appendServerHistoryIntoMessages, STORAGE_KEYS, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
9
- export { createNxtlinqApi, getAiAgentApiHost, startVoiceSession, createVoiceApi, useDraggable, useLocalStorage, useSessionStorage, useResizable, synthesizeSpeechToBuffer, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, connectWallet, disconnectWallet, validateToken, createAITMetadata, generateAITId, prepareAITCreation, createNotification, getNotificationIcon, containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './legacy';
9
+ export { createNxtlinqApi, getAiAgentApiHost, startVoiceSession, createVoiceApi, useDraggable, useLocalStorage, useSessionStorage, useResizable, synthesizeSpeechToBuffer, streamSpeechToAudioContext, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, connectWallet, disconnectWallet, validateToken, createAITMetadata, generateAITId, prepareAITCreation, createNotification, getNotificationIcon, containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './legacy';
@@ -11,4 +11,14 @@ export declare function synthesizeSpeechToBuffer(params: {
11
11
  buffer: ArrayBuffer;
12
12
  mimeType: string;
13
13
  } | undefined>;
14
+ export declare function streamSpeechToAudioContext(params: {
15
+ text: string;
16
+ apiKey: string;
17
+ apiSecret: string;
18
+ audioCtx: AudioContext;
19
+ onSourceScheduled?: (source: AudioBufferSourceNode) => void;
20
+ onFirstChunk?: () => void;
21
+ onEnded?: () => void;
22
+ signal?: AbortSignal;
23
+ }): Promise<void>;
14
24
  //# sourceMappingURL=textToSpeech.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textToSpeech.d.ts","sourceRoot":"","sources":["../../../../src/legacy/core/lib/textToSpeech.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AA8E3F;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CACnC,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAsCjE"}
1
+ {"version":3,"file":"textToSpeech.d.ts","sourceRoot":"","sources":["../../../../src/legacy/core/lib/textToSpeech.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AA8E3F;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CACnC,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAsCjE;AAmBD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,YAAY,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC5D,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DhB"}
@@ -1,4 +1,5 @@
1
1
  import { SpeechConfig, SpeechSynthesizer } from 'microsoft-cognitiveservices-speech-sdk';
2
+ import { streamTextTts } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
3
  import { getAiAgentApiHost } from '../../api/nxtlinq-api';
3
4
  const DEFAULT_AZURE_VOICE = 'en-US-JennyNeural';
4
5
  async function fetchAzureTokenFromAgent(agentHost, apiKey, apiSecret) {
@@ -80,3 +81,79 @@ export async function synthesizeSpeechToBuffer(params) {
80
81
  return undefined;
81
82
  return { buffer, mimeType: 'audio/wav' };
82
83
  }
84
+ /**
85
+ * Stream OpenAI TTS audio progressively into a Web AudioContext.
86
+ *
87
+ * Receives raw PCM16 (24 kHz mono) chunks from the server and schedules
88
+ * them back-to-back on the AudioContext so playback starts as soon as the
89
+ * first chunk arrives, before the full audio is downloaded.
90
+ *
91
+ * @param onSourceScheduled Called for every AudioBufferSourceNode that is
92
+ * scheduled — collect these to stop playback early (e.g. user clicks stop).
93
+ * @param onFirstChunk Called when the first audio chunk is ready to play.
94
+ * @param onEnded Called when all scheduled buffers have finished playing.
95
+ */
96
+ // Number of chunks to pre-buffer before starting playback. Each chunk is
97
+ // ~8192 bytes = ~170ms of 24kHz PCM16 audio. Buffering 3 chunks gives ~510ms
98
+ // of headroom to absorb network jitter and prevent gaps at the start.
99
+ const PREBUFFER_CHUNKS = 3;
100
+ export async function streamSpeechToAudioContext(params) {
101
+ const { text, apiKey, apiSecret, audioCtx, onSourceScheduled, onFirstChunk, onEnded, signal } = params;
102
+ if (audioCtx.state === 'suspended')
103
+ await audioCtx.resume();
104
+ let nextPlayTime = 0;
105
+ let pendingCount = 0;
106
+ let streamDone = false;
107
+ // Pre-buffer: collect initial chunks before scheduling any playback so the
108
+ // AudioContext has enough backlog to play without gaps from network jitter.
109
+ const prebuffer = [];
110
+ let prebuffering = true;
111
+ const checkEnded = () => {
112
+ if (streamDone && pendingCount === 0)
113
+ onEnded?.();
114
+ };
115
+ const scheduleBuffer = (audioBuffer) => {
116
+ const source = audioCtx.createBufferSource();
117
+ source.buffer = audioBuffer;
118
+ source.connect(audioCtx.destination);
119
+ pendingCount++;
120
+ source.start(nextPlayTime);
121
+ nextPlayTime += audioBuffer.duration;
122
+ onSourceScheduled?.(source);
123
+ source.onended = () => {
124
+ pendingCount--;
125
+ checkEnded();
126
+ };
127
+ };
128
+ const flushPrebuffer = () => {
129
+ nextPlayTime = audioCtx.currentTime + 0.05;
130
+ onFirstChunk?.();
131
+ for (const buf of prebuffer)
132
+ scheduleBuffer(buf);
133
+ prebuffer.length = 0;
134
+ prebuffering = false;
135
+ };
136
+ await streamTextTts({ apiKey, apiSecret, text }, (pcm16) => {
137
+ if (signal?.aborted)
138
+ return;
139
+ const float32 = new Float32Array(pcm16.length);
140
+ for (let i = 0; i < pcm16.length; i++)
141
+ float32[i] = pcm16[i] / 32768;
142
+ const audioBuffer = audioCtx.createBuffer(1, float32.length, 24000);
143
+ audioBuffer.copyToChannel(float32, 0);
144
+ if (prebuffering) {
145
+ prebuffer.push(audioBuffer);
146
+ if (prebuffer.length >= PREBUFFER_CHUNKS)
147
+ flushPrebuffer();
148
+ }
149
+ else {
150
+ scheduleBuffer(audioBuffer);
151
+ }
152
+ }, signal);
153
+ // Stream finished — flush any remaining prebuffered chunks (short text that
154
+ // never reached PREBUFFER_CHUNKS) and mark done.
155
+ if (prebuffering && prebuffer.length > 0)
156
+ flushPrebuffer();
157
+ streamDone = true;
158
+ checkEnded();
159
+ }
@@ -6,7 +6,7 @@ export { default as useLocalStorage } from './core/lib/useLocalStorage';
6
6
  export { default as useSessionStorage } from './core/lib/useSessionStorage';
7
7
  export { useResizable } from './core/lib/useResizable';
8
8
  export type { ResizeCorner } from './core/lib/useResizable';
9
- export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
9
+ export { synthesizeSpeechToBuffer, streamSpeechToAudioContext } from './core/lib/textToSpeech';
10
10
  export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
11
11
  export { useVoiceMode } from './core/lib/useVoiceMode';
12
12
  export { default as metakeepClient } from './core/metakeepClient';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,mBAAmB,aAAa,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,eAAe,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,mBAAmB,aAAa,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,eAAe,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
@@ -4,7 +4,7 @@ export { useDraggable } from './core/lib/useDraggable';
4
4
  export { default as useLocalStorage } from './core/lib/useLocalStorage';
5
5
  export { default as useSessionStorage } from './core/lib/useSessionStorage';
6
6
  export { useResizable } from './core/lib/useResizable';
7
- export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
7
+ export { synthesizeSpeechToBuffer, streamSpeechToAudioContext } from './core/lib/textToSpeech';
8
8
  export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
9
9
  export { useVoiceMode } from './core/lib/useVoiceMode';
10
10
  export { default as metakeepClient } from './core/metakeepClient';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytexbyte/nxtlinq-ai-agent-web-development",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "React Web headless SDK for nxtlinq AI Agent — hooks and browser ports",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "react-dom": ">=18.0.0"
38
38
  },
39
39
  "dependencies": {
40
- "@bytexbyte/nxtlinq-ai-agent-core-development": "0.4.1",
40
+ "@bytexbyte/nxtlinq-ai-agent-core-development": "0.4.2",
41
41
  "ethers": "^6.16.0",
42
42
  "fast-json-stable-stringify": "^2.1.0",
43
43
  "metakeep": "^2.2.8",
@@ -18,6 +18,8 @@ export type UseNxtlinqAgentResult = NxtlinqAgentSnapshot & {
18
18
  setMessages: (messages: Message[]) => void;
19
19
  postTextTts: (text: string) => Promise<PostTextTtsResult>;
20
20
  buildTextTtsPlaybackUri: (result: PostTextTtsResult) => string;
21
+ /** Stream PCM16 TTS using browser fetch streaming. Web-only. */
22
+ streamTextTts: (text: string, onChunk: (pcm16: Int16Array) => void, signal?: AbortSignal) => Promise<void>;
21
23
  };
22
24
 
23
25
  /** Subscribe to {@link NxtlinqAgent} state for custom Web chat UIs. */
@@ -60,6 +62,12 @@ export function useNxtlinqAgent(): UseNxtlinqAgentResult {
60
62
  [agent],
61
63
  );
62
64
 
65
+ const streamTextTts = useCallback(
66
+ (text: string, onChunk: (pcm16: Int16Array) => void, signal?: AbortSignal) =>
67
+ agent.streamTextTts(text, onChunk, signal),
68
+ [agent],
69
+ );
70
+
63
71
  return {
64
72
  agent,
65
73
  ...snapshot,
@@ -69,5 +77,6 @@ export function useNxtlinqAgent(): UseNxtlinqAgentResult {
69
77
  setMessages,
70
78
  postTextTts,
71
79
  buildTextTtsPlaybackUri,
80
+ streamTextTts,
72
81
  };
73
82
  }
package/src/index.ts CHANGED
@@ -62,6 +62,7 @@ export {
62
62
  useSessionStorage,
63
63
  useResizable,
64
64
  synthesizeSpeechToBuffer,
65
+ streamSpeechToAudioContext,
65
66
  useSpeechToTextFromMic,
66
67
  useVoiceMode,
67
68
  metakeepClient,
@@ -1,4 +1,5 @@
1
1
  import { SpeechConfig, SpeechSynthesisResult, SpeechSynthesizer } from 'microsoft-cognitiveservices-speech-sdk';
2
+ import { streamTextTts } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
3
  import { getAiAgentApiHost } from '../../api/nxtlinq-api';
3
4
  import type { ClientTtsVoiceSettings } from '@bytexbyte/nxtlinq-ai-agent-core-development';
4
5
 
@@ -125,3 +126,94 @@ export async function synthesizeSpeechToBuffer(params: {
125
126
  if (!buffer) return undefined;
126
127
  return { buffer, mimeType: 'audio/wav' };
127
128
  }
129
+
130
+ /**
131
+ * Stream OpenAI TTS audio progressively into a Web AudioContext.
132
+ *
133
+ * Receives raw PCM16 (24 kHz mono) chunks from the server and schedules
134
+ * them back-to-back on the AudioContext so playback starts as soon as the
135
+ * first chunk arrives, before the full audio is downloaded.
136
+ *
137
+ * @param onSourceScheduled Called for every AudioBufferSourceNode that is
138
+ * scheduled — collect these to stop playback early (e.g. user clicks stop).
139
+ * @param onFirstChunk Called when the first audio chunk is ready to play.
140
+ * @param onEnded Called when all scheduled buffers have finished playing.
141
+ */
142
+ // Number of chunks to pre-buffer before starting playback. Each chunk is
143
+ // ~8192 bytes = ~170ms of 24kHz PCM16 audio. Buffering 3 chunks gives ~510ms
144
+ // of headroom to absorb network jitter and prevent gaps at the start.
145
+ const PREBUFFER_CHUNKS = 3;
146
+
147
+ export async function streamSpeechToAudioContext(params: {
148
+ text: string;
149
+ apiKey: string;
150
+ apiSecret: string;
151
+ audioCtx: AudioContext;
152
+ onSourceScheduled?: (source: AudioBufferSourceNode) => void;
153
+ onFirstChunk?: () => void;
154
+ onEnded?: () => void;
155
+ signal?: AbortSignal;
156
+ }): Promise<void> {
157
+ const { text, apiKey, apiSecret, audioCtx, onSourceScheduled, onFirstChunk, onEnded, signal } =
158
+ params;
159
+
160
+ if (audioCtx.state === 'suspended') await audioCtx.resume();
161
+
162
+ let nextPlayTime = 0;
163
+ let pendingCount = 0;
164
+ let streamDone = false;
165
+ // Pre-buffer: collect initial chunks before scheduling any playback so the
166
+ // AudioContext has enough backlog to play without gaps from network jitter.
167
+ const prebuffer: AudioBuffer[] = [];
168
+ let prebuffering = true;
169
+
170
+ const checkEnded = () => {
171
+ if (streamDone && pendingCount === 0) onEnded?.();
172
+ };
173
+
174
+ const scheduleBuffer = (audioBuffer: AudioBuffer) => {
175
+ const source = audioCtx.createBufferSource();
176
+ source.buffer = audioBuffer;
177
+ source.connect(audioCtx.destination);
178
+ pendingCount++;
179
+ source.start(nextPlayTime);
180
+ nextPlayTime += audioBuffer.duration;
181
+ onSourceScheduled?.(source);
182
+ source.onended = () => {
183
+ pendingCount--;
184
+ checkEnded();
185
+ };
186
+ };
187
+
188
+ const flushPrebuffer = () => {
189
+ nextPlayTime = audioCtx.currentTime + 0.05;
190
+ onFirstChunk?.();
191
+ for (const buf of prebuffer) scheduleBuffer(buf);
192
+ prebuffer.length = 0;
193
+ prebuffering = false;
194
+ };
195
+
196
+ await streamTextTts({ apiKey, apiSecret, text }, (pcm16: Int16Array) => {
197
+ if (signal?.aborted) return;
198
+
199
+ const float32 = new Float32Array(pcm16.length);
200
+ for (let i = 0; i < pcm16.length; i++) float32[i] = pcm16[i] / 32768;
201
+
202
+ const audioBuffer = audioCtx.createBuffer(1, float32.length, 24000);
203
+ audioBuffer.copyToChannel(float32, 0);
204
+
205
+ if (prebuffering) {
206
+ prebuffer.push(audioBuffer);
207
+ if (prebuffer.length >= PREBUFFER_CHUNKS) flushPrebuffer();
208
+ } else {
209
+ scheduleBuffer(audioBuffer);
210
+ }
211
+ }, signal);
212
+
213
+ // Stream finished — flush any remaining prebuffered chunks (short text that
214
+ // never reached PREBUFFER_CHUNKS) and mark done.
215
+ if (prebuffering && prebuffer.length > 0) flushPrebuffer();
216
+
217
+ streamDone = true;
218
+ checkEnded();
219
+ }
@@ -7,7 +7,7 @@ export { default as useLocalStorage } from './core/lib/useLocalStorage';
7
7
  export { default as useSessionStorage } from './core/lib/useSessionStorage';
8
8
  export { useResizable } from './core/lib/useResizable';
9
9
  export type { ResizeCorner } from './core/lib/useResizable';
10
- export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
10
+ export { synthesizeSpeechToBuffer, streamSpeechToAudioContext } from './core/lib/textToSpeech';
11
11
  export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
12
12
  export { useVoiceMode } from './core/lib/useVoiceMode';
13
13