@bytexbyte/nxtlinq-ai-agent-ui-react-native-development 0.2.0 → 0.3.1

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.
Files changed (104) hide show
  1. package/dist/NxtlinqAgentAssistant.d.ts +4 -4
  2. package/dist/NxtlinqAgentAssistant.d.ts.map +1 -1
  3. package/dist/NxtlinqAgentAssistant.js +5 -6
  4. package/dist/components/AgentAssistantShell.d.ts +1 -3
  5. package/dist/components/AgentAssistantShell.d.ts.map +1 -1
  6. package/dist/components/AgentAssistantShell.js +3 -7
  7. package/dist/components/AgentMessageList.d.ts.map +1 -1
  8. package/dist/components/AgentMessageList.js +7 -9
  9. package/dist/components/AgentVoiceBar.d.ts.map +1 -1
  10. package/dist/components/AgentVoiceBar.js +14 -34
  11. package/dist/components/MessageAttachmentPreview.d.ts +10 -0
  12. package/dist/components/MessageAttachmentPreview.d.ts.map +1 -0
  13. package/dist/components/MessageAttachmentPreview.js +15 -0
  14. package/dist/components/VoiceAddMediaModal.d.ts +12 -0
  15. package/dist/components/VoiceAddMediaModal.d.ts.map +1 -0
  16. package/dist/components/VoiceAddMediaModal.js +31 -0
  17. package/dist/components/VoiceAttachmentButton.d.ts +3 -0
  18. package/dist/components/VoiceAttachmentButton.d.ts.map +1 -0
  19. package/dist/components/VoiceAttachmentButton.js +58 -0
  20. package/dist/components/VoiceIcons.d.ts +1 -0
  21. package/dist/components/VoiceIcons.d.ts.map +1 -1
  22. package/dist/components/VoiceIcons.js +3 -0
  23. package/dist/components/VoiceWaveform.d.ts +2 -2
  24. package/dist/components/VoiceWaveform.d.ts.map +1 -1
  25. package/dist/components/VoiceWaveform.js +16 -5
  26. package/dist/components/useMessageListAutoScroll.d.ts +12 -0
  27. package/dist/components/useMessageListAutoScroll.d.ts.map +1 -0
  28. package/dist/components/useMessageListAutoScroll.js +42 -0
  29. package/dist/context/AgentAssistantContext.d.ts +3 -3
  30. package/dist/context/AgentAssistantContext.d.ts.map +1 -1
  31. package/dist/context/AgentAssistantContext.js +76 -29
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -1
  35. package/dist/types.d.ts +3 -8
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/voice/float32ToPcm16.d.ts +2 -0
  38. package/dist/voice/float32ToPcm16.d.ts.map +1 -0
  39. package/dist/voice/float32ToPcm16.js +8 -0
  40. package/dist/voice/loadImageCropPicker.d.ts +11 -0
  41. package/dist/voice/loadImageCropPicker.d.ts.map +1 -0
  42. package/dist/voice/loadImageCropPicker.js +12 -0
  43. package/dist/voice/sendVoiceImageAttachment.d.ts +15 -0
  44. package/dist/voice/sendVoiceImageAttachment.d.ts.map +1 -0
  45. package/dist/voice/sendVoiceImageAttachment.js +29 -0
  46. package/dist/voice/useVoiceImagePicker.d.ts +11 -0
  47. package/dist/voice/useVoiceImagePicker.d.ts.map +1 -0
  48. package/dist/voice/useVoiceImagePicker.js +38 -0
  49. package/dist/voice/useVoiceMicState.d.ts +4 -0
  50. package/dist/voice/useVoiceMicState.d.ts.map +1 -1
  51. package/dist/voice/useVoiceMicState.js +32 -3
  52. package/dist/voice/useVoiceSilenceCommit.d.ts +10 -0
  53. package/dist/voice/useVoiceSilenceCommit.d.ts.map +1 -0
  54. package/dist/voice/useVoiceSilenceCommit.js +76 -0
  55. package/dist/voice/useVoiceTranscriptMessages.d.ts +16 -0
  56. package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -0
  57. package/dist/voice/useVoiceTranscriptMessages.js +129 -0
  58. package/dist/voice/useWsRealtimeAudio.d.ts +17 -0
  59. package/dist/voice/useWsRealtimeAudio.d.ts.map +1 -0
  60. package/dist/voice/useWsRealtimeAudio.js +165 -0
  61. package/dist/voice/voiceImagePickerOptions.d.ts +11 -0
  62. package/dist/voice/voiceImagePickerOptions.d.ts.map +1 -0
  63. package/dist/voice/voiceImagePickerOptions.js +10 -0
  64. package/dist/voice/voiceSilenceConstants.d.ts +8 -0
  65. package/dist/voice/voiceSilenceConstants.d.ts.map +1 -0
  66. package/dist/voice/voiceSilenceConstants.js +7 -0
  67. package/dist/voice/wsPcmPlayer.d.ts +24 -0
  68. package/dist/voice/wsPcmPlayer.d.ts.map +1 -0
  69. package/dist/voice/wsPcmPlayer.js +146 -0
  70. package/dist/voice/wsPcmRecorder.d.ts +26 -0
  71. package/dist/voice/wsPcmRecorder.d.ts.map +1 -0
  72. package/dist/voice/wsPcmRecorder.js +145 -0
  73. package/dist/voice/wsRealtimeConstants.d.ts +2 -0
  74. package/dist/voice/wsRealtimeConstants.d.ts.map +1 -0
  75. package/dist/voice/wsRealtimeConstants.js +1 -0
  76. package/package.json +8 -5
  77. package/src/NxtlinqAgentAssistant.tsx +3 -12
  78. package/src/components/AgentAssistantShell.tsx +2 -18
  79. package/src/components/AgentMessageList.tsx +18 -15
  80. package/src/components/AgentVoiceBar.tsx +35 -70
  81. package/src/components/MessageAttachmentPreview.tsx +43 -0
  82. package/src/components/VoiceAddMediaModal.tsx +69 -0
  83. package/src/components/VoiceAttachmentButton.tsx +100 -0
  84. package/src/components/VoiceIcons.tsx +4 -0
  85. package/src/components/VoiceWaveform.tsx +15 -5
  86. package/src/components/useMessageListAutoScroll.ts +57 -0
  87. package/src/context/AgentAssistantContext.tsx +100 -32
  88. package/src/index.ts +2 -2
  89. package/src/react-native.d.ts +18 -1
  90. package/src/types.ts +3 -8
  91. package/src/voice/float32ToPcm16.ts +8 -0
  92. package/src/voice/loadImageCropPicker.ts +18 -0
  93. package/src/voice/sendVoiceImageAttachment.ts +49 -0
  94. package/src/voice/useVoiceImagePicker.ts +54 -0
  95. package/src/voice/useVoiceMicState.ts +38 -3
  96. package/src/voice/useVoiceSilenceCommit.ts +94 -0
  97. package/src/voice/useVoiceTranscriptMessages.ts +173 -0
  98. package/src/voice/useWsRealtimeAudio.ts +200 -0
  99. package/src/voice/voiceImagePickerOptions.ts +10 -0
  100. package/src/voice/voiceSilenceConstants.ts +10 -0
  101. package/src/voice/wsPcmPlayer.ts +166 -0
  102. package/src/voice/wsPcmRecorder.ts +152 -0
  103. package/src/voice/wsRealtimeConstants.ts +1 -0
  104. package/src/components/AgentRemoteAudio.tsx +0 -105
@@ -3,6 +3,8 @@ import { useNxtlinqAgent, useNxtlinqVoice, } from '@bytexbyte/nxtlinq-ai-agent-r
3
3
  import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
4
4
  import { defaultAgentAssistantTheme } from '../theme/defaultTheme';
5
5
  import { useVoiceMicState } from '../voice/useVoiceMicState';
6
+ import { useVoiceTranscriptMessages } from '../voice/useVoiceTranscriptMessages';
7
+ import { useWsRealtimeAudio } from '../voice/useWsRealtimeAudio';
6
8
  import { waitForIOSVoiceCaptureRelease } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
7
9
  import { isTextTtsPlayerSupported, TextTtsPlayer } from '../voice/TextTtsPlayer';
8
10
  const AgentAssistantContext = createContext(null);
@@ -27,32 +29,49 @@ export function AgentAssistantProvider({ children, ui, }) {
27
29
  const textTtsPlayerRef = React.useRef(null);
28
30
  const ttsRequestIdRef = React.useRef(0);
29
31
  const voiceConnectChainRef = React.useRef(Promise.resolve());
30
- const isVoiceAvailable = Boolean(ui.webrtcEnabled && ui.enableVoice !== false);
32
+ const isVoiceAvailable = ui.enableVoice !== false;
31
33
  const micStartsMuted = ui.startWithMicMuted ?? true;
32
- const { isMicMuted, isMicHeldForAssistant, toggleVoiceMicMute, prepareForVoiceConnect, resetMicState, clearAssistantMicHold, } = useVoiceMicState(voice, isVoiceConnecting, {
34
+ const handleVoiceBargeIn = useCallback(() => {
35
+ voice.interrupt();
36
+ }, [voice]);
37
+ const { isMicMuted, isCaptureActive, isMicHeldForAssistant, toggleVoiceMicMute, muteAfterSilenceCommit, prepareForVoiceConnect, resetMicState, clearAssistantMicHold, } = useVoiceMicState(voice, isVoiceConnecting, {
33
38
  startWithMicMuted: micStartsMuted,
34
- // Decoupled: open-mic demo can start unmuted yet still mute during assistant TTS.
35
39
  holdMicDuringAssistant: ui.holdMicDuringAssistant ?? true,
40
+ onBargeIn: handleVoiceBargeIn,
41
+ });
42
+ const isVoiceActive = voice.voiceSessionId != null;
43
+ const { buildCallbacks: buildWsAudioCallbacks, bindSession: bindWsAudioSession, getOutputAudioLevel: getWsOutputAudioLevel, } = useWsRealtimeAudio(isCaptureActive, isVoiceActive, {
44
+ voiceStatus: voice.voiceStatus,
45
+ muteAfterSilenceCommit,
36
46
  });
47
+ const getOutputAudioLevel = useCallback(() => getWsOutputAudioLevel(), [getWsOutputAudioLevel]);
48
+ const voiceTranscriptApi = useMemo(() => ({
49
+ getMessages: () => agent.agent.getSnapshot().messages,
50
+ setMessages: agent.setMessages,
51
+ syncVoiceTurnHistory: agent.syncVoiceTurnHistory,
52
+ }), [agent.agent, agent.setMessages, agent.syncVoiceTurnHistory]);
53
+ const { handleTranscript, handleDone, clearVoiceStream } = useVoiceTranscriptMessages(voiceTranscriptApi, interactionMode, voice.voiceSessionId);
37
54
  const setInteractionMode = useCallback((mode) => {
38
55
  if (mode === 'text' && interactionMode === 'voice') {
39
56
  void (async () => {
40
57
  await voice.stopVoice('switch_to_text');
41
58
  await waitForIOSVoiceCaptureRelease();
59
+ clearVoiceStream();
42
60
  resetMicState();
43
61
  })();
44
62
  }
45
63
  setInteractionModeState(mode);
46
- }, [interactionMode, voice, resetMicState]);
64
+ }, [interactionMode, voice, resetMicState, clearVoiceStream]);
47
65
  useEffect(() => {
48
66
  if (interactionMode === 'text' && voice.voiceSessionId != null) {
49
67
  void (async () => {
50
68
  await voice.stopVoice('mode_text_cleanup');
51
69
  await waitForIOSVoiceCaptureRelease();
70
+ clearVoiceStream();
52
71
  resetMicState();
53
72
  })();
54
73
  }
55
- }, [interactionMode, voice.voiceSessionId, voice, resetMicState]);
74
+ }, [interactionMode, voice.voiceSessionId, voice, resetMicState, clearVoiceStream]);
56
75
  const sendText = useCallback(async () => {
57
76
  const text = inputText.trim();
58
77
  if (!text || agent.isLoading)
@@ -80,32 +99,46 @@ export function AgentAssistantProvider({ children, ui, }) {
80
99
  try {
81
100
  const session = await voice.startVoice({
82
101
  startWithMicMuted: micStartsMuted,
83
- ...options,
84
- onOpen: () => {
85
- if (!micStartsMuted) {
86
- voice.muteMic(false);
87
- }
88
- options?.onOpen?.();
89
- },
90
- onClose: (reason) => {
91
- resetMicState();
92
- const userInitiated = reason === 'switch_to_text' ||
93
- reason === 'client_stop' ||
94
- reason === 'mode_text_cleanup';
95
- if (userInitiated) {
96
- setInteractionModeState('text');
97
- }
98
- else {
99
- console.warn('[nxtlinq] voice session closed:', reason);
100
- }
101
- options?.onClose?.(reason);
102
+ keepMicCaptureActive: true,
103
+ ...buildWsAudioCallbacks({
104
+ ...options,
105
+ onOpen: () => {
106
+ if (!micStartsMuted) {
107
+ voice.muteMic(false);
108
+ }
109
+ options?.onOpen?.();
110
+ },
111
+ onClose: (reason) => {
112
+ clearVoiceStream();
113
+ resetMicState();
114
+ const userInitiated = reason === 'switch_to_text' ||
115
+ reason === 'client_stop' ||
116
+ reason === 'mode_text_cleanup';
117
+ if (userInitiated) {
118
+ setInteractionModeState('text');
119
+ }
120
+ else {
121
+ console.warn('[nxtlinq] voice session closed:', reason);
122
+ }
123
+ options?.onClose?.(reason);
124
+ },
125
+ onError: (err) => {
126
+ clearVoiceStream();
127
+ resetMicState();
128
+ console.warn('[nxtlinq] voice session error:', err.message);
129
+ options?.onError?.(err);
130
+ },
131
+ }),
132
+ onTranscript: (event) => {
133
+ handleTranscript(event);
134
+ options?.onTranscript?.(event);
102
135
  },
103
- onError: (err) => {
104
- resetMicState();
105
- console.warn('[nxtlinq] voice session error:', err.message);
106
- options?.onError?.(err);
136
+ onDone: (event) => {
137
+ handleDone(event);
138
+ options?.onDone?.(event);
107
139
  },
108
140
  });
141
+ bindWsAudioSession(session, !micStartsMuted);
109
142
  return session;
110
143
  }
111
144
  catch (err) {
@@ -120,7 +153,17 @@ export function AgentAssistantProvider({ children, ui, }) {
120
153
  const next = voiceConnectChainRef.current.then(connect, connect);
121
154
  voiceConnectChainRef.current = next.catch(() => undefined);
122
155
  return next;
123
- }, [voice, prepareForVoiceConnect, resetMicState, micStartsMuted, ui.voiceRemoteAudioGain]);
156
+ }, [
157
+ voice,
158
+ prepareForVoiceConnect,
159
+ resetMicState,
160
+ clearVoiceStream,
161
+ micStartsMuted,
162
+ buildWsAudioCallbacks,
163
+ bindWsAudioSession,
164
+ handleTranscript,
165
+ handleDone,
166
+ ]);
124
167
  const wrappedStopVoice = useCallback(async () => {
125
168
  await voice.stopVoice('client_stop');
126
169
  await waitForIOSVoiceCaptureRelease();
@@ -165,6 +208,7 @@ export function AgentAssistantProvider({ children, ui, }) {
165
208
  const value = useMemo(() => ({
166
209
  ...agent,
167
210
  ...voice,
211
+ getOutputAudioLevel,
168
212
  isVoiceActive: interactionMode === 'voice' && voice.voiceSessionId != null,
169
213
  startVoice: wrappedStartVoice,
170
214
  stopVoice: wrappedStopVoice,
@@ -183,6 +227,7 @@ export function AgentAssistantProvider({ children, ui, }) {
183
227
  isVoiceAvailable,
184
228
  isVoiceConnecting,
185
229
  isVoiceChannelReady,
230
+ showVoiceImageInput: ui.showVoiceImageInput ?? false,
186
231
  postTextTts,
187
232
  buildTextTtsPlaybackUri,
188
233
  playMessageTts,
@@ -193,6 +238,7 @@ export function AgentAssistantProvider({ children, ui, }) {
193
238
  }), [
194
239
  agent,
195
240
  voice,
241
+ getOutputAudioLevel,
196
242
  wrappedStartVoice,
197
243
  wrappedStopVoice,
198
244
  wrappedInterrupt,
@@ -208,6 +254,7 @@ export function AgentAssistantProvider({ children, ui, }) {
208
254
  isVoiceAvailable,
209
255
  isVoiceConnecting,
210
256
  isVoiceChannelReady,
257
+ ui.showVoiceImageInput,
211
258
  postTextTts,
212
259
  buildTextTtsPlaybackUri,
213
260
  playMessageTts,
package/dist/index.d.ts CHANGED
@@ -5,12 +5,13 @@ export { AgentAssistantProvider, useAgentAssistant, voiceStatusLabel, type Agent
5
5
  export { AgentMessageList } from './components/AgentMessageList';
6
6
  export { AgentComposer } from './components/AgentComposer';
7
7
  export { AgentVoiceBar } from './components/AgentVoiceBar';
8
- export { AgentRemoteAudio } from './components/AgentRemoteAudio';
9
8
  export { AudioSessionWaker } from './voice/AudioSessionWaker';
10
9
  export { PresetMessageChips } from './components/PresetMessageChips';
11
10
  export { AgentAssistantShell } from './components/AgentAssistantShell';
12
11
  export { VoiceGreetTrigger } from './components/VoiceGreetTrigger';
13
- export { NxtlinqAgentProvider, useNxtlinqAgent, useNxtlinqVoice, createNxtlinqAgentRN, type NxtlinqAgentProviderProps, type RNWebRTCModule, } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
12
+ export { VoiceAttachmentButton } from './components/VoiceAttachmentButton';
13
+ export { VoiceImageInput } from './components/VoiceImageInput';
14
+ export { NxtlinqAgentProvider, useNxtlinqAgent, useNxtlinqVoice, createNxtlinqAgentRN, type NxtlinqAgentProviderProps, } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
14
15
  export type { AgentEnvironment, AgentConfig, Attachment, AgentResponse, SendMessageOptions, NxtlinqAgentSnapshot, StartVoiceSessionOptions, VoiceSession, VoiceStatus, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
15
16
  export { NxtlinqAgent, setApiHosts, VoiceNotSupportedError, mapServerHistoryToMessages, appendServerHistoryIntoMessages, STORAGE_KEYS, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,aAAa,EACb,OAAO,EACP,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,GACrB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,GACpB,MAAM,sDAAsD,CAAC;AAE9D,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,YAAY,EACZ,WAAW,GACZ,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,YAAY,GACb,MAAM,8CAA8C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,aAAa,EACb,OAAO,EACP,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,GACrB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAG/D,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,KAAK,yBAAyB,GAC/B,MAAM,sDAAsD,CAAC;AAE9D,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,YAAY,EACZ,WAAW,GACZ,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,YAAY,GACb,MAAM,8CAA8C,CAAC"}
package/dist/index.js CHANGED
@@ -4,11 +4,12 @@ export { AgentAssistantProvider, useAgentAssistant, voiceStatusLabel, } from './
4
4
  export { AgentMessageList } from './components/AgentMessageList';
5
5
  export { AgentComposer } from './components/AgentComposer';
6
6
  export { AgentVoiceBar } from './components/AgentVoiceBar';
7
- export { AgentRemoteAudio } from './components/AgentRemoteAudio';
8
7
  export { AudioSessionWaker } from './voice/AudioSessionWaker';
9
8
  export { PresetMessageChips } from './components/PresetMessageChips';
10
9
  export { AgentAssistantShell } from './components/AgentAssistantShell';
11
10
  export { VoiceGreetTrigger } from './components/VoiceGreetTrigger';
11
+ export { VoiceAttachmentButton } from './components/VoiceAttachmentButton';
12
+ export { VoiceImageInput } from './components/VoiceImageInput';
12
13
  // Headless SDK re-exports for apps that compose custom layouts
13
14
  export { NxtlinqAgentProvider, useNxtlinqAgent, useNxtlinqVoice, createNxtlinqAgentRN, } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
14
15
  export { NxtlinqAgent, setApiHosts, VoiceNotSupportedError, mapServerHistoryToMessages, appendServerHistoryIntoMessages, STORAGE_KEYS, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
package/dist/types.d.ts CHANGED
@@ -45,11 +45,11 @@ export type NxtlinqAgentAssistantProps = Omit<NxtlinqAgentProviderProps, 'childr
45
45
  title?: string;
46
46
  placeholder?: string;
47
47
  presetMessages?: PresetMessage[];
48
- /** Fetch history on mount when `pseudoId` is set. */
48
+ /** Fetch history on mount when identity is configured (`pseudoId`, and optionally `conversationId`). */
49
49
  loadHistoryOnMount?: boolean;
50
50
  /** Max messages to prefetch (default 50). */
51
51
  historyLast?: number;
52
- /** Show voice mode controls (requires `webrtcModule`). */
52
+ /** Show voice mode controls. @default true */
53
53
  enableVoice?: boolean;
54
54
  /** Start in voice interaction mode. */
55
55
  startInVoiceMode?: boolean;
@@ -73,16 +73,11 @@ export type NxtlinqAgentAssistantProps = Omit<NxtlinqAgentProviderProps, 'childr
73
73
  voiceAutoGreet?: VoiceAutoGreetConfig | boolean;
74
74
  /**
75
75
  * iOS: bundled silent MP3 (`require('./assets/silent.mp3')`) to wake AVAudioSession.
76
- * Strongly recommended — without it WebRTC/TTS may be silent on cold start.
76
+ * Strongly recommended — without it voice/TTS may be silent on cold start.
77
77
  */
78
78
  iosSilentAudioSource?: number | {
79
79
  uri: string;
80
80
  };
81
- /**
82
- * Voice-mode assistant playback gain (WebRTC `_setVolume`, 0–10). Default 5 to
83
- * match text TTS loudness (react-native-video volume 1). Default 10.
84
- */
85
- voiceRemoteAudioGain?: number;
86
81
  /** Text-mode TTS volume (react-native-video, 0–1). @default 1 */
87
82
  textTtsVolume?: number;
88
83
  theme?: Partial<AgentAssistantTheme>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function float32ToPcm16(input: Float32Array): Int16Array;
2
+ //# sourceMappingURL=float32ToPcm16.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"float32ToPcm16.d.ts","sourceRoot":"","sources":["../../src/voice/float32ToPcm16.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,CAO9D"}
@@ -0,0 +1,8 @@
1
+ export function float32ToPcm16(input) {
2
+ const out = new Int16Array(input.length);
3
+ for (let i = 0; i < input.length; i += 1) {
4
+ const sample = Math.max(-1, Math.min(1, input[i]));
5
+ out[i] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
6
+ }
7
+ return out;
8
+ }
@@ -0,0 +1,11 @@
1
+ type PickerResult = {
2
+ path?: string;
3
+ };
4
+ type ImageCropPickerModule = {
5
+ openPicker: (options: Record<string, unknown>) => Promise<PickerResult>;
6
+ openCamera: (options: Record<string, unknown>) => Promise<PickerResult>;
7
+ };
8
+ export declare function loadImageCropPicker(): ImageCropPickerModule | null;
9
+ export declare function toFileUri(path: string): string;
10
+ export {};
11
+ //# sourceMappingURL=loadImageCropPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadImageCropPicker.d.ts","sourceRoot":"","sources":["../../src/voice/loadImageCropPicker.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtC,KAAK,qBAAqB,GAAG;IAC3B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACxE,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACzE,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,qBAAqB,GAAG,IAAI,CAOlE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
@@ -0,0 +1,12 @@
1
+ export function loadImageCropPicker() {
2
+ try {
3
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
4
+ return require('react-native-image-crop-picker');
5
+ }
6
+ catch {
7
+ return null;
8
+ }
9
+ }
10
+ export function toFileUri(path) {
11
+ return path.startsWith('file://') ? path : `file://${path}`;
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { Message } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+ import { uriToVoiceImageAttachment } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
3
+ type SendVoiceImageAttachmentParams = {
4
+ fileUri: string;
5
+ messages: Message[];
6
+ voiceSessionId: string | null;
7
+ setMessages: (messages: Message[]) => void;
8
+ sendVoiceUserInput: (options: {
9
+ attachments: Awaited<ReturnType<typeof uriToVoiceImageAttachment>>[];
10
+ clientMessageId?: string;
11
+ }) => void;
12
+ };
13
+ export declare function sendVoiceImageAttachment({ fileUri, messages, voiceSessionId, setMessages, sendVoiceUserInput, }: SendVoiceImageAttachmentParams): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=sendVoiceImageAttachment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendVoiceImageAttachment.d.ts","sourceRoot":"","sources":["../../src/voice/sendVoiceImageAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8CAA8C,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sDAAsD,CAAC;AAEjG,KAAK,8BAA8B,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAC3C,kBAAkB,EAAE,CAAC,OAAO,EAAE;QAC5B,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,EAAE,CAAC;QACrE,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,KAAK,IAAI,CAAC;CACZ,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,EAC7C,OAAO,EACP,QAAQ,EACR,cAAc,EACd,WAAW,EACX,kBAAkB,GACnB,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BhD"}
@@ -0,0 +1,29 @@
1
+ import { uriToVoiceImageAttachment } from '@bytexbyte/nxtlinq-ai-agent-react-native-development';
2
+ export async function sendVoiceImageAttachment({ fileUri, messages, voiceSessionId, setMessages, sendVoiceUserInput, }) {
3
+ const clientMessageId = `user_img_${Date.now()}`;
4
+ const previewAttachment = {
5
+ type: 'image',
6
+ url: fileUri,
7
+ name: 'image.jpg',
8
+ mimeType: 'image/jpeg',
9
+ };
10
+ setMessages([
11
+ ...messages,
12
+ {
13
+ id: clientMessageId,
14
+ role: 'user',
15
+ content: '',
16
+ timestamp: new Date().toISOString(),
17
+ attachments: [previewAttachment],
18
+ metadata: voiceSessionId ? { voiceRealtime: true, voiceSessionId } : undefined,
19
+ },
20
+ ]);
21
+ try {
22
+ const attachment = await uriToVoiceImageAttachment(fileUri, 'voice-image.jpg');
23
+ sendVoiceUserInput({ attachments: [attachment], clientMessageId });
24
+ }
25
+ catch (error) {
26
+ setMessages(messages.filter((message) => message.id !== clientMessageId));
27
+ throw error;
28
+ }
29
+ }
@@ -0,0 +1,11 @@
1
+ type UseVoiceImagePickerResult = {
2
+ modalVisible: boolean;
3
+ openModal: () => void;
4
+ closeModal: () => void;
5
+ pickFromLibrary: () => Promise<void>;
6
+ takePhoto: () => Promise<void>;
7
+ pickerMissing: boolean;
8
+ };
9
+ export declare function useVoiceImagePicker(onPicked?: (fileUri: string) => void): UseVoiceImagePickerResult;
10
+ export {};
11
+ //# sourceMappingURL=useVoiceImagePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVoiceImagePicker.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceImagePicker.ts"],"names":[],"mappings":"AAKA,KAAK,yBAAyB,GAAG;IAC/B,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACnC,yBAAyB,CAqC3B"}
@@ -0,0 +1,38 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { InteractionManager } from 'react-native';
3
+ import { loadImageCropPicker, toFileUri } from './loadImageCropPicker';
4
+ import { VOICE_IMAGE_PICKER_OPTIONS } from './voiceImagePickerOptions';
5
+ export function useVoiceImagePicker(onPicked) {
6
+ const [modalVisible, setModalVisible] = useState(false);
7
+ const pickerMissing = loadImageCropPicker() == null;
8
+ const runPick = useCallback(async (mode) => {
9
+ const picker = loadImageCropPicker();
10
+ if (!picker)
11
+ return;
12
+ try {
13
+ const result = mode === 'library'
14
+ ? await picker.openPicker({ ...VOICE_IMAGE_PICKER_OPTIONS })
15
+ : await picker.openCamera({ ...VOICE_IMAGE_PICKER_OPTIONS });
16
+ if (!result?.path) {
17
+ setModalVisible(false);
18
+ return;
19
+ }
20
+ const uri = toFileUri(result.path);
21
+ setModalVisible(false);
22
+ InteractionManager.runAfterInteractions(() => {
23
+ onPicked?.(uri);
24
+ });
25
+ }
26
+ catch {
27
+ setModalVisible(false);
28
+ }
29
+ }, [onPicked]);
30
+ return {
31
+ modalVisible,
32
+ openModal: useCallback(() => setModalVisible(true), []),
33
+ closeModal: useCallback(() => setModalVisible(false), []),
34
+ pickFromLibrary: useCallback(() => runPick('library'), [runPick]),
35
+ takePhoto: useCallback(() => runPick('camera'), [runPick]),
36
+ pickerMissing,
37
+ };
38
+ }
@@ -12,11 +12,15 @@ export type UseVoiceMicStateOptions = {
12
12
  * Use with open-mic demos (`startWithMicMuted={false}`). Berify hold-to-talk keeps this true.
13
13
  */
14
14
  holdMicDuringAssistant?: boolean;
15
+ /** Called when user opens mic while assistant is responding (barge-in / interrupt). */
16
+ onBargeIn?: () => void;
15
17
  };
16
18
  export declare function useVoiceMicState(voice: UseNxtlinqVoiceResult, isVoiceConnecting: boolean, options?: UseVoiceMicStateOptions): {
17
19
  isMicMuted: boolean;
20
+ isCaptureActive: boolean;
18
21
  isMicHeldForAssistant: boolean;
19
22
  toggleVoiceMicMute: () => void;
23
+ muteAfterSilenceCommit: () => void;
20
24
  prepareForVoiceConnect: () => void;
21
25
  resetMicState: () => void;
22
26
  clearAssistantMicHold: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useVoiceMicState.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceMicState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAIlG;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,iGAAiG;IACjG,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,qBAAqB,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,OAAO,CAAC,EAAE,uBAAuB;;;;;;;EA4FlC"}
1
+ {"version":3,"file":"useVoiceMicState.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceMicState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAIlG;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,iGAAiG;IACjG,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,qBAAqB,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,OAAO,CAAC,EAAE,uBAAuB;;;;;;;;;EA6HlC"}
@@ -7,22 +7,32 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
7
7
  const assistantMicHoldRef = useRef(false);
8
8
  const userMicOptInRef = useRef(!connectMuted);
9
9
  const [isMicMuted, setIsMicMuted] = useState(connectMuted);
10
+ const [isCaptureActive, setIsCaptureActive] = useState(!connectMuted);
11
+ const syncCaptureActive = useCallback(() => {
12
+ const userOpen = !userMicMutedRef.current;
13
+ const holdBlocks = assistantMicHoldRef.current
14
+ && !(userMicOptInRef.current && userOpen);
15
+ setIsCaptureActive(userOpen && !holdBlocks);
16
+ }, []);
10
17
  const applyMicState = useCallback(() => {
11
18
  const shouldMute = userMicMutedRef.current || assistantMicHoldRef.current;
12
19
  voice.muteMic(shouldMute);
13
20
  setIsMicMuted(shouldMute);
14
- }, [voice]);
21
+ syncCaptureActive();
22
+ }, [voice, syncCaptureActive]);
15
23
  const resetMicState = useCallback(() => {
16
24
  userMicMutedRef.current = false;
17
25
  assistantMicHoldRef.current = false;
18
26
  userMicOptInRef.current = false;
19
27
  setIsMicMuted(false);
28
+ setIsCaptureActive(false);
20
29
  }, []);
21
30
  const prepareForVoiceConnect = useCallback(() => {
22
31
  userMicMutedRef.current = connectMuted;
23
32
  userMicOptInRef.current = !connectMuted;
24
33
  assistantMicHoldRef.current = false;
25
34
  setIsMicMuted(connectMuted);
35
+ setIsCaptureActive(!connectMuted);
26
36
  voice.muteMic(connectMuted);
27
37
  }, [voice, connectMuted]);
28
38
  useEffect(() => {
@@ -31,6 +41,7 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
31
41
  userMicMutedRef.current = connectMuted;
32
42
  voice.muteMic(connectMuted);
33
43
  setIsMicMuted(connectMuted);
44
+ setIsCaptureActive(!connectMuted);
34
45
  }, [isVoiceConnecting, voice, connectMuted]);
35
46
  const prevVoiceStatusRef = useRef(voice.voiceStatus);
36
47
  useEffect(() => {
@@ -38,6 +49,11 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
38
49
  const prev = prevVoiceStatusRef.current;
39
50
  prevVoiceStatusRef.current = status;
40
51
  if (holdDuringAssistant && ASSISTANT_MIC_HOLD_STATUSES.has(status)) {
52
+ if (!userMicMutedRef.current && userMicOptInRef.current) {
53
+ assistantMicHoldRef.current = false;
54
+ applyMicState();
55
+ return;
56
+ }
41
57
  assistantMicHoldRef.current = true;
42
58
  applyMicState();
43
59
  return;
@@ -53,11 +69,22 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
53
69
  applyMicState();
54
70
  }
55
71
  }, [voice.voiceStatus, applyMicState, holdDuringAssistant]);
72
+ const muteAfterSilenceCommit = useCallback(() => {
73
+ userMicMutedRef.current = true;
74
+ userMicOptInRef.current = false;
75
+ applyMicState();
76
+ }, [applyMicState]);
56
77
  const toggleVoiceMicMute = useCallback(() => {
57
78
  if (!voice.isVoiceActive && !isVoiceConnecting)
58
79
  return;
59
- if (assistantMicHoldRef.current && userMicMutedRef.current)
80
+ if (assistantMicHoldRef.current && userMicMutedRef.current) {
81
+ assistantMicHoldRef.current = false;
82
+ userMicMutedRef.current = false;
83
+ userMicOptInRef.current = true;
84
+ options?.onBargeIn?.();
85
+ applyMicState();
60
86
  return;
87
+ }
61
88
  if (assistantMicHoldRef.current) {
62
89
  userMicMutedRef.current = true;
63
90
  applyMicState();
@@ -67,7 +94,7 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
67
94
  userMicMutedRef.current = nextMuted;
68
95
  userMicOptInRef.current = !nextMuted;
69
96
  applyMicState();
70
- }, [voice.isVoiceActive, isVoiceConnecting, applyMicState]);
97
+ }, [voice.isVoiceActive, isVoiceConnecting, applyMicState, options]);
71
98
  const clearAssistantMicHold = useCallback(() => {
72
99
  assistantMicHoldRef.current = false;
73
100
  applyMicState();
@@ -75,8 +102,10 @@ export function useVoiceMicState(voice, isVoiceConnecting, options) {
75
102
  const isMicHeldForAssistant = ASSISTANT_MIC_HOLD_STATUSES.has(voice.voiceStatus);
76
103
  return {
77
104
  isMicMuted,
105
+ isCaptureActive,
78
106
  isMicHeldForAssistant,
79
107
  toggleVoiceMicMute,
108
+ muteAfterSilenceCommit,
80
109
  prepareForVoiceConnect,
81
110
  resetMicState,
82
111
  clearAssistantMicHold,
@@ -0,0 +1,10 @@
1
+ import type { VoiceSession, VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+ export declare function useVoiceSilenceCommit(getSession: () => VoiceSession | null, onMutedAfterCommit: () => void, voiceStatus: VoiceStatus): {
3
+ startPoll: () => void;
4
+ clearPoll: () => void;
5
+ resetTurn: () => void;
6
+ onSpeechRms: (rms: number) => void;
7
+ tryCommit: (reason: "silence" | "manual") => void;
8
+ consumeSkipCommitOnMute: () => boolean;
9
+ };
10
+ //# sourceMappingURL=useVoiceSilenceCommit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVoiceSilenceCommit.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceSilenceCommit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAU9F,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,YAAY,GAAG,IAAI,EACrC,kBAAkB,EAAE,MAAM,IAAI,EAC9B,WAAW,EAAE,WAAW;;;;uBAwDc,MAAM;wBApCjC,SAAS,GAAG,QAAQ;;EA4DhC"}
@@ -0,0 +1,76 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { ASSISTANT_MIC_HOLD_STATUSES } from './voiceMicConstants';
3
+ import { MIC_BARGE_IN_RMS_THRESHOLD, MIC_SILENCE_COMMIT_MS, MIC_SILENCE_POLL_MS, MIC_SPEECH_RMS_THRESHOLD, } from './voiceSilenceConstants';
4
+ export function useVoiceSilenceCommit(getSession, onMutedAfterCommit, voiceStatus) {
5
+ const lastSpeechAtRef = useRef(0);
6
+ const hadSpeechRef = useRef(false);
7
+ const commitInFlightRef = useRef(false);
8
+ const skipCommitOnMuteRef = useRef(false);
9
+ const pollRef = useRef(null);
10
+ const voiceStatusRef = useRef(voiceStatus);
11
+ voiceStatusRef.current = voiceStatus;
12
+ const onMutedAfterCommitRef = useRef(onMutedAfterCommit);
13
+ onMutedAfterCommitRef.current = onMutedAfterCommit;
14
+ const clearPoll = useCallback(() => {
15
+ if (pollRef.current) {
16
+ clearInterval(pollRef.current);
17
+ pollRef.current = null;
18
+ }
19
+ }, []);
20
+ const tryCommit = useCallback((reason) => {
21
+ const session = getSession();
22
+ if (commitInFlightRef.current)
23
+ return;
24
+ if (!hadSpeechRef.current) {
25
+ session?.clearInputAudio?.();
26
+ return;
27
+ }
28
+ commitInFlightRef.current = true;
29
+ hadSpeechRef.current = false;
30
+ session?.commitInputAudio?.();
31
+ if (reason === 'silence') {
32
+ skipCommitOnMuteRef.current = true;
33
+ onMutedAfterCommitRef.current();
34
+ }
35
+ queueMicrotask(() => {
36
+ commitInFlightRef.current = false;
37
+ });
38
+ }, [getSession]);
39
+ const startPoll = useCallback(() => {
40
+ clearPoll();
41
+ pollRef.current = setInterval(() => {
42
+ if (commitInFlightRef.current || !hadSpeechRef.current)
43
+ return;
44
+ if (Date.now() - lastSpeechAtRef.current < MIC_SILENCE_COMMIT_MS)
45
+ return;
46
+ tryCommit('silence');
47
+ }, MIC_SILENCE_POLL_MS);
48
+ }, [clearPoll, tryCommit]);
49
+ const resetTurn = useCallback(() => {
50
+ hadSpeechRef.current = false;
51
+ lastSpeechAtRef.current = Date.now();
52
+ getSession()?.clearInputAudio?.();
53
+ }, [getSession]);
54
+ const onSpeechRms = useCallback((rms) => {
55
+ const threshold = ASSISTANT_MIC_HOLD_STATUSES.has(voiceStatusRef.current)
56
+ ? MIC_BARGE_IN_RMS_THRESHOLD
57
+ : MIC_SPEECH_RMS_THRESHOLD;
58
+ if (rms >= threshold) {
59
+ lastSpeechAtRef.current = Date.now();
60
+ hadSpeechRef.current = true;
61
+ }
62
+ }, []);
63
+ const consumeSkipCommitOnMute = useCallback(() => {
64
+ const skip = skipCommitOnMuteRef.current;
65
+ skipCommitOnMuteRef.current = false;
66
+ return skip;
67
+ }, []);
68
+ return {
69
+ startPoll,
70
+ clearPoll,
71
+ resetTurn,
72
+ onSpeechRms,
73
+ tryCommit,
74
+ consumeSkipCommitOnMute,
75
+ };
76
+ }