@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.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 (142) hide show
  1. package/dist/NxtlinqAgentChat.d.ts +26 -0
  2. package/dist/NxtlinqAgentChat.d.ts.map +1 -0
  3. package/dist/NxtlinqAgentChat.js +28 -0
  4. package/dist/components/AgentAssistantShell.d.ts +5 -0
  5. package/dist/components/AgentAssistantShell.d.ts.map +1 -0
  6. package/dist/components/AgentAssistantShell.js +52 -0
  7. package/dist/components/AgentComposer.d.ts +3 -0
  8. package/dist/components/AgentComposer.d.ts.map +1 -0
  9. package/dist/components/AgentComposer.js +60 -0
  10. package/dist/components/AgentMessageList.d.ts +3 -0
  11. package/dist/components/AgentMessageList.d.ts.map +1 -0
  12. package/dist/components/AgentMessageList.js +37 -0
  13. package/dist/components/AgentRemoteAudio.d.ts +4 -0
  14. package/dist/components/AgentRemoteAudio.d.ts.map +1 -0
  15. package/dist/components/AgentRemoteAudio.js +34 -0
  16. package/dist/components/AgentVoiceBar.d.ts +3 -0
  17. package/dist/components/AgentVoiceBar.d.ts.map +1 -0
  18. package/dist/components/AgentVoiceBar.js +91 -0
  19. package/dist/components/PresetMessageChips.d.ts +3 -0
  20. package/dist/components/PresetMessageChips.d.ts.map +1 -0
  21. package/dist/components/PresetMessageChips.js +23 -0
  22. package/dist/context/AgentAssistantContext.d.ts +32 -0
  23. package/dist/context/AgentAssistantContext.d.ts.map +1 -0
  24. package/dist/context/AgentAssistantContext.js +159 -0
  25. package/dist/index.d.ts +16 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +12 -0
  28. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts +2 -0
  29. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts.map +1 -0
  30. package/dist/legacy/assets/images/adiSideItalicDataUri.js +1 -0
  31. package/dist/legacy/chatbot/ChatBot.d.ts +5 -0
  32. package/dist/legacy/chatbot/ChatBot.d.ts.map +1 -0
  33. package/dist/legacy/chatbot/ChatBot.js +35 -0
  34. package/dist/legacy/chatbot/context/ChatBotContext.d.ts +5 -0
  35. package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +1 -0
  36. package/dist/legacy/chatbot/context/ChatBotContext.js +2908 -0
  37. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts +166 -0
  38. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts.map +1 -0
  39. package/dist/legacy/chatbot/types/ChatBotTypes.js +1 -0
  40. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts +17 -0
  41. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts.map +1 -0
  42. package/dist/legacy/chatbot/ui/BerifyMeModal.js +110 -0
  43. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts +3 -0
  44. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts.map +1 -0
  45. package/dist/legacy/chatbot/ui/ChatBotUI.js +625 -0
  46. package/dist/legacy/chatbot/ui/MessageInput.d.ts +3 -0
  47. package/dist/legacy/chatbot/ui/MessageInput.d.ts.map +1 -0
  48. package/dist/legacy/chatbot/ui/MessageInput.js +321 -0
  49. package/dist/legacy/chatbot/ui/MessageList.d.ts +4 -0
  50. package/dist/legacy/chatbot/ui/MessageList.d.ts.map +1 -0
  51. package/dist/legacy/chatbot/ui/MessageList.js +455 -0
  52. package/dist/legacy/chatbot/ui/ModelSelector.d.ts +4 -0
  53. package/dist/legacy/chatbot/ui/ModelSelector.d.ts.map +1 -0
  54. package/dist/legacy/chatbot/ui/ModelSelector.js +122 -0
  55. package/dist/legacy/chatbot/ui/NotificationModal.d.ts +15 -0
  56. package/dist/legacy/chatbot/ui/NotificationModal.d.ts.map +1 -0
  57. package/dist/legacy/chatbot/ui/NotificationModal.js +53 -0
  58. package/dist/legacy/chatbot/ui/PermissionForm.d.ts +8 -0
  59. package/dist/legacy/chatbot/ui/PermissionForm.d.ts.map +1 -0
  60. package/dist/legacy/chatbot/ui/PermissionForm.js +465 -0
  61. package/dist/legacy/chatbot/ui/PresetMessages.d.ts +4 -0
  62. package/dist/legacy/chatbot/ui/PresetMessages.d.ts.map +1 -0
  63. package/dist/legacy/chatbot/ui/PresetMessages.js +33 -0
  64. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts +3 -0
  65. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts.map +1 -0
  66. package/dist/legacy/chatbot/ui/VoiceModePanel.js +95 -0
  67. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts +73 -0
  68. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts.map +1 -0
  69. package/dist/legacy/chatbot/ui/styles/isolatedStyles.js +985 -0
  70. package/dist/legacy/index.d.ts +14 -0
  71. package/dist/legacy/index.d.ts.map +1 -0
  72. package/dist/legacy/index.js +12 -0
  73. package/dist/theme/defaultTheme.d.ts +3 -0
  74. package/dist/theme/defaultTheme.d.ts.map +1 -0
  75. package/dist/theme/defaultTheme.js +20 -0
  76. package/dist/types.d.ts +62 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +1 -0
  79. package/dist/voice/useVoiceConnectOrchestration.d.ts +21 -0
  80. package/dist/voice/useVoiceConnectOrchestration.d.ts.map +1 -0
  81. package/dist/voice/useVoiceConnectOrchestration.js +86 -0
  82. package/dist/voice/useVoiceMicState.d.ts +15 -0
  83. package/dist/voice/useVoiceMicState.d.ts.map +1 -0
  84. package/dist/voice/useVoiceMicState.js +94 -0
  85. package/dist/voice/useVoiceSilenceCommit.d.ts +10 -0
  86. package/dist/voice/useVoiceSilenceCommit.d.ts.map +1 -0
  87. package/dist/voice/useVoiceSilenceCommit.js +67 -0
  88. package/dist/voice/useVoiceTranscriptMessages.d.ts +16 -0
  89. package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -0
  90. package/dist/voice/useVoiceTranscriptMessages.js +129 -0
  91. package/dist/voice/useWsRealtimeAudio.d.ts +18 -0
  92. package/dist/voice/useWsRealtimeAudio.d.ts.map +1 -0
  93. package/dist/voice/useWsRealtimeAudio.js +102 -0
  94. package/dist/voice/voiceMicConstants.d.ts +4 -0
  95. package/dist/voice/voiceMicConstants.d.ts.map +1 -0
  96. package/dist/voice/voiceMicConstants.js +10 -0
  97. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts +23 -0
  98. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts.map +1 -0
  99. package/dist/voice/ws/BrowserWsPcmPlayer.js +137 -0
  100. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts +17 -0
  101. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts.map +1 -0
  102. package/dist/voice/ws/BrowserWsPcmRecorder.js +71 -0
  103. package/dist/voice/ws/float32ToPcm16.d.ts +2 -0
  104. package/dist/voice/ws/float32ToPcm16.d.ts.map +1 -0
  105. package/dist/voice/ws/float32ToPcm16.js +8 -0
  106. package/dist/voice/ws/voiceSilenceConstants.d.ts +5 -0
  107. package/dist/voice/ws/voiceSilenceConstants.d.ts.map +1 -0
  108. package/dist/voice/ws/voiceSilenceConstants.js +4 -0
  109. package/dist/voice/ws/wsRealtimeConstants.d.ts +2 -0
  110. package/dist/voice/ws/wsRealtimeConstants.d.ts.map +1 -0
  111. package/dist/voice/ws/wsRealtimeConstants.js +1 -0
  112. package/package.json +60 -0
  113. package/src/NxtlinqAgentChat.tsx +79 -0
  114. package/src/components/AgentAssistantShell.tsx +104 -0
  115. package/src/components/AgentComposer.tsx +134 -0
  116. package/src/components/AgentMessageList.tsx +78 -0
  117. package/src/components/AgentRemoteAudio.tsx +34 -0
  118. package/src/components/AgentVoiceBar.tsx +173 -0
  119. package/src/components/PresetMessageChips.tsx +41 -0
  120. package/src/context/AgentAssistantContext.tsx +276 -0
  121. package/src/index.ts +78 -0
  122. package/src/legacy/assets/images/adiSideItalicDataUri.ts +1 -0
  123. package/src/legacy/chatbot/ChatBot.tsx +61 -0
  124. package/src/legacy/chatbot/context/ChatBotContext.tsx +3227 -0
  125. package/src/legacy/chatbot/types/ChatBotTypes.ts +195 -0
  126. package/src/legacy/chatbot/ui/BerifyMeModal.tsx +145 -0
  127. package/src/legacy/chatbot/ui/ChatBotUI.tsx +949 -0
  128. package/src/legacy/chatbot/ui/MessageInput.tsx +517 -0
  129. package/src/legacy/chatbot/ui/MessageList.tsx +764 -0
  130. package/src/legacy/chatbot/ui/ModelSelector.tsx +190 -0
  131. package/src/legacy/chatbot/ui/NotificationModal.tsx +110 -0
  132. package/src/legacy/chatbot/ui/PermissionForm.tsx +632 -0
  133. package/src/legacy/chatbot/ui/PresetMessages.tsx +50 -0
  134. package/src/legacy/chatbot/ui/VoiceModePanel.tsx +168 -0
  135. package/src/legacy/chatbot/ui/styles/isolatedStyles.ts +1058 -0
  136. package/src/legacy/index.ts +26 -0
  137. package/src/theme/defaultTheme.ts +22 -0
  138. package/src/types.ts +65 -0
  139. package/src/voice/useVoiceConnectOrchestration.ts +117 -0
  140. package/src/voice/useVoiceMicState.ts +117 -0
  141. package/src/voice/useVoiceTranscriptMessages.ts +173 -0
  142. package/src/voice/voiceMicConstants.ts +13 -0
@@ -0,0 +1,102 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { BrowserWsPcmPlayer } from './ws/BrowserWsPcmPlayer';
3
+ import { BrowserWsPcmRecorder } from './ws/BrowserWsPcmRecorder';
4
+ import { useVoiceSilenceCommit } from './useVoiceSilenceCommit';
5
+ export function useWsRealtimeAudio(isCaptureActive, isVoiceActive, options) {
6
+ const playerRef = useRef(null);
7
+ const recorderRef = useRef(null);
8
+ const sessionRef = useRef(null);
9
+ const isCaptureActiveRef = useRef(isCaptureActive);
10
+ isCaptureActiveRef.current = isCaptureActive;
11
+ const prevCaptureActiveRef = useRef(isCaptureActive);
12
+ const getSession = useCallback(() => sessionRef.current, []);
13
+ const muteAfterSilenceCommitRef = useRef(options.muteAfterSilenceCommit);
14
+ muteAfterSilenceCommitRef.current = options.muteAfterSilenceCommit;
15
+ const silence = useVoiceSilenceCommit(getSession, () => muteAfterSilenceCommitRef.current(), options.voiceStatus);
16
+ const silenceRef = useRef(silence);
17
+ silenceRef.current = silence;
18
+ const ensurePlayer = useCallback(async () => {
19
+ if (!playerRef.current) {
20
+ playerRef.current = new BrowserWsPcmPlayer();
21
+ playerRef.current.prewarm();
22
+ }
23
+ await playerRef.current.ensureRunning();
24
+ }, []);
25
+ const stopCapture = useCallback((commit) => {
26
+ const s = silenceRef.current;
27
+ s.clearPoll();
28
+ recorderRef.current?.stop();
29
+ if (!commit)
30
+ return;
31
+ if (s.consumeSkipCommitOnMute())
32
+ return;
33
+ s.tryCommit('manual');
34
+ }, []);
35
+ const startCapture = useCallback(async () => {
36
+ const session = sessionRef.current;
37
+ if (!session)
38
+ return;
39
+ const s = silenceRef.current;
40
+ if (!recorderRef.current)
41
+ recorderRef.current = new BrowserWsPcmRecorder();
42
+ playerRef.current?.clearQueue();
43
+ recorderRef.current.bindSession(session);
44
+ recorderRef.current.setOnRms(s.onSpeechRms);
45
+ s.resetTurn();
46
+ try {
47
+ await recorderRef.current.start();
48
+ s.startPoll();
49
+ }
50
+ catch (err) {
51
+ s.clearPoll();
52
+ console.error('[nxtlinq] mic capture start failed', err);
53
+ }
54
+ }, []);
55
+ const cleanup = useCallback(() => {
56
+ silenceRef.current.clearPoll();
57
+ recorderRef.current?.cleanup();
58
+ recorderRef.current = null;
59
+ playerRef.current?.cleanup();
60
+ playerRef.current = null;
61
+ sessionRef.current = null;
62
+ }, []);
63
+ const bindSession = useCallback((session, captureWhenUnmuted = false) => {
64
+ sessionRef.current = session;
65
+ recorderRef.current?.bindSession(session);
66
+ if (session && captureWhenUnmuted && isCaptureActiveRef.current) {
67
+ void startCapture();
68
+ }
69
+ }, [startCapture]);
70
+ const buildCallbacks = useCallback((overrides) => ({
71
+ onOpen: () => {
72
+ void ensurePlayer();
73
+ overrides?.onOpen?.();
74
+ },
75
+ onAudioDelta: (pcm16) => {
76
+ void ensurePlayer().then(() => playerRef.current?.addAudio(pcm16));
77
+ overrides?.onAudioDelta?.(pcm16);
78
+ },
79
+ onClose: (reason) => {
80
+ cleanup();
81
+ overrides?.onClose?.(reason);
82
+ },
83
+ onError: (err) => {
84
+ cleanup();
85
+ overrides?.onError?.(err);
86
+ },
87
+ }), [cleanup, ensurePlayer]);
88
+ useEffect(() => {
89
+ if (!isVoiceActive) {
90
+ prevCaptureActiveRef.current = false;
91
+ cleanup();
92
+ return;
93
+ }
94
+ const prev = prevCaptureActiveRef.current;
95
+ prevCaptureActiveRef.current = isCaptureActive;
96
+ if (isCaptureActive && !prev)
97
+ void startCapture();
98
+ else if (!isCaptureActive && prev)
99
+ stopCapture(true);
100
+ }, [isCaptureActive, isVoiceActive, startCapture, stopCapture, cleanup]);
101
+ return { bindSession, buildCallbacks, getOutputAudioLevel: () => playerRef.current?.getAudioLevel() ?? 0 };
102
+ }
@@ -0,0 +1,4 @@
1
+ import type { VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+ export declare const ASSISTANT_MIC_HOLD_STATUSES: ReadonlySet<VoiceStatus>;
3
+ export declare const SPEAKER_ACTIVE_STATUSES: ReadonlySet<VoiceStatus>;
4
+ //# sourceMappingURL=voiceMicConstants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceMicConstants.d.ts","sourceRoot":"","sources":["../../src/voice/voiceMicConstants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAEhF,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,WAAW,CAK/D,CAAC;AAEH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,WAAW,CAG3D,CAAC"}
@@ -0,0 +1,10 @@
1
+ export const ASSISTANT_MIC_HOLD_STATUSES = new Set([
2
+ 'transcribing',
3
+ 'thinking',
4
+ 'generating',
5
+ 'speaking',
6
+ ]);
7
+ export const SPEAKER_ACTIVE_STATUSES = new Set([
8
+ 'generating',
9
+ 'speaking',
10
+ ]);
@@ -0,0 +1,23 @@
1
+ export declare class BrowserWsPcmPlayer {
2
+ private readonly sampleRate;
3
+ private readonly fadeSamples;
4
+ private audioContext;
5
+ private gainNode;
6
+ private analyserNode;
7
+ private analyserBuffer;
8
+ private readonly activeSources;
9
+ private queue;
10
+ private isPlaying;
11
+ private playHead;
12
+ private lastChunkRms;
13
+ private lastChunkAt;
14
+ ensureRunning(): Promise<void>;
15
+ prewarm(): void;
16
+ addAudio(pcm16: Int16Array | ArrayBuffer): void;
17
+ getAudioLevel(): number;
18
+ clearQueue(): void;
19
+ cleanup(): void;
20
+ private ensureContext;
21
+ private pipelineQueue;
22
+ }
23
+ //# sourceMappingURL=BrowserWsPcmPlayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserWsPcmPlayer.d.ts","sourceRoot":"","sources":["../../../src/voice/ws/BrowserWsPcmPlayer.ts"],"names":[],"mappings":"AAeA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmD;IAC/E,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAK;IAElB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpC,OAAO,IAAI,IAAI;IAIf,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAqB/C,aAAa,IAAI,MAAM;IAevB,UAAU,IAAI,IAAI;IAWlB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,aAAa;CA2BtB"}
@@ -0,0 +1,137 @@
1
+ import { WS_REALTIME_SAMPLE_RATE } from './wsRealtimeConstants';
2
+ const LEVEL_SCALE = 3.2;
3
+ const LOOKAHEAD_SECONDS = 0.02;
4
+ const CROSSFADE_SECONDS = 0.04;
5
+ function applyFade(buffer, fadeSamples) {
6
+ const fade = Math.min(fadeSamples, Math.floor(buffer.length / 2));
7
+ if (fade <= 0)
8
+ return;
9
+ for (let i = 0; i < fade; i += 1) {
10
+ buffer[i] *= i / fade;
11
+ buffer[buffer.length - 1 - i] *= (fade - i) / fade;
12
+ }
13
+ }
14
+ export class BrowserWsPcmPlayer {
15
+ constructor() {
16
+ this.sampleRate = WS_REALTIME_SAMPLE_RATE;
17
+ this.fadeSamples = Math.max(1, Math.floor(this.sampleRate * 0.01));
18
+ this.audioContext = null;
19
+ this.gainNode = null;
20
+ this.analyserNode = null;
21
+ this.analyserBuffer = null;
22
+ this.activeSources = new Set();
23
+ this.queue = [];
24
+ this.isPlaying = true;
25
+ this.playHead = 0;
26
+ this.lastChunkRms = 0;
27
+ this.lastChunkAt = 0;
28
+ }
29
+ async ensureRunning() {
30
+ const ctx = this.ensureContext();
31
+ if (ctx.state === 'suspended')
32
+ await ctx.resume();
33
+ this.isPlaying = true;
34
+ }
35
+ prewarm() {
36
+ this.ensureContext();
37
+ }
38
+ addAudio(pcm16) {
39
+ const ctx = this.ensureContext();
40
+ const int16 = pcm16 instanceof ArrayBuffer ? new Int16Array(pcm16) : pcm16;
41
+ if (int16.length === 0)
42
+ return;
43
+ const floats = new Float32Array(int16.length);
44
+ let sumSq = 0;
45
+ for (let i = 0; i < int16.length; i += 1) {
46
+ const sample = int16[i] / 32768;
47
+ floats[i] = Math.max(-1, Math.min(1, sample));
48
+ sumSq += sample * sample;
49
+ }
50
+ applyFade(floats, this.fadeSamples);
51
+ this.lastChunkRms = Math.min(1, Math.sqrt(sumSq / int16.length) * LEVEL_SCALE);
52
+ this.lastChunkAt = Date.now();
53
+ const buffer = ctx.createBuffer(1, floats.length, this.sampleRate);
54
+ buffer.getChannelData(0).set(floats);
55
+ this.queue.push(buffer);
56
+ this.isPlaying = true;
57
+ this.pipelineQueue();
58
+ }
59
+ getAudioLevel() {
60
+ const analyser = this.analyserNode;
61
+ const buf = this.analyserBuffer;
62
+ if (analyser && buf && this.activeSources.size > 0) {
63
+ analyser.getFloatTimeDomainData(buf);
64
+ let sumSq = 0;
65
+ for (let i = 0; i < buf.length; i += 1) {
66
+ const v = buf[i];
67
+ sumSq += v * v;
68
+ }
69
+ return Math.min(1, Math.sqrt(sumSq / buf.length) * LEVEL_SCALE);
70
+ }
71
+ return Date.now() - this.lastChunkAt < 500 ? this.lastChunkRms : 0;
72
+ }
73
+ clearQueue() {
74
+ for (const source of this.activeSources) {
75
+ try {
76
+ source.stop();
77
+ }
78
+ catch { /* noop */ }
79
+ }
80
+ this.activeSources.clear();
81
+ this.queue = [];
82
+ this.playHead = this.audioContext?.currentTime ?? 0;
83
+ this.lastChunkRms = 0;
84
+ this.lastChunkAt = 0;
85
+ }
86
+ cleanup() {
87
+ this.clearQueue();
88
+ this.isPlaying = false;
89
+ void this.audioContext?.close();
90
+ this.analyserNode = null;
91
+ this.analyserBuffer = null;
92
+ this.gainNode = null;
93
+ this.audioContext = null;
94
+ }
95
+ ensureContext() {
96
+ if (!this.audioContext) {
97
+ this.audioContext = new AudioContext({ sampleRate: this.sampleRate });
98
+ this.gainNode = this.audioContext.createGain();
99
+ this.analyserNode = this.audioContext.createAnalyser();
100
+ this.analyserNode.fftSize = 512;
101
+ this.analyserBuffer = new Float32Array(this.analyserNode.fftSize);
102
+ this.gainNode.connect(this.analyserNode);
103
+ this.analyserNode.connect(this.audioContext.destination);
104
+ this.playHead = this.audioContext.currentTime;
105
+ }
106
+ return this.audioContext;
107
+ }
108
+ pipelineQueue() {
109
+ const ctx = this.audioContext;
110
+ const gain = this.gainNode;
111
+ if (!ctx || !gain || !this.isPlaying)
112
+ return;
113
+ while (this.queue.length > 0) {
114
+ const audioBuffer = this.queue.shift();
115
+ if (!audioBuffer)
116
+ return;
117
+ const source = ctx.createBufferSource();
118
+ source.buffer = audioBuffer;
119
+ const perSourceGain = ctx.createGain();
120
+ source.connect(perSourceGain);
121
+ perSourceGain.connect(gain);
122
+ const now = ctx.currentTime;
123
+ const startAt = Math.max((this.playHead || now) - CROSSFADE_SECONDS * 0.75, now + LOOKAHEAD_SECONDS);
124
+ const duration = audioBuffer.duration;
125
+ const endAt = startAt + duration;
126
+ const fade = Math.min(CROSSFADE_SECONDS, duration / 2);
127
+ perSourceGain.gain.setValueAtTime(0, startAt);
128
+ perSourceGain.gain.linearRampToValueAtTime(1, startAt + fade);
129
+ perSourceGain.gain.setValueAtTime(1, endAt - fade);
130
+ perSourceGain.gain.linearRampToValueAtTime(0, endAt);
131
+ this.playHead = endAt;
132
+ this.activeSources.add(source);
133
+ source.start(startAt);
134
+ source.onended = () => this.activeSources.delete(source);
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,17 @@
1
+ import type { VoiceSession } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+ /** getUserMedia only on start() — Berify hold-to-talk aligned. */
3
+ export declare class BrowserWsPcmRecorder {
4
+ private session;
5
+ private onRms?;
6
+ private stream;
7
+ private audioContext;
8
+ private processor;
9
+ private source;
10
+ private silentGain;
11
+ bindSession(session: VoiceSession | null): void;
12
+ setOnRms(handler: (rms: number) => void): void;
13
+ start(): Promise<void>;
14
+ stop(): void;
15
+ cleanup(): void;
16
+ }
17
+ //# sourceMappingURL=BrowserWsPcmRecorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserWsPcmRecorder.d.ts","sourceRoot":"","sources":["../../../src/voice/ws/BrowserWsPcmRecorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAajF,kEAAkE;AAClE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,KAAK,CAAC,CAAwB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,UAAU,CAAyB;IAE3C,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAI/C,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B,IAAI,IAAI,IAAI;IAgBZ,OAAO,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,71 @@
1
+ import { float32ToPcm16 } from './float32ToPcm16';
2
+ import { WS_REALTIME_SAMPLE_RATE } from './wsRealtimeConstants';
3
+ function computeRms(channel) {
4
+ let sum = 0;
5
+ for (let i = 0; i < channel.length; i += 1) {
6
+ const v = channel[i];
7
+ sum += v * v;
8
+ }
9
+ return channel.length > 0 ? Math.sqrt(sum / channel.length) : 0;
10
+ }
11
+ /** getUserMedia only on start() — Berify hold-to-talk aligned. */
12
+ export class BrowserWsPcmRecorder {
13
+ constructor() {
14
+ this.session = null;
15
+ this.stream = null;
16
+ this.audioContext = null;
17
+ this.processor = null;
18
+ this.source = null;
19
+ this.silentGain = null;
20
+ }
21
+ bindSession(session) {
22
+ this.session = session;
23
+ }
24
+ setOnRms(handler) {
25
+ this.onRms = handler;
26
+ }
27
+ async start() {
28
+ if (this.processor)
29
+ return;
30
+ this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
31
+ const ctx = new AudioContext({ sampleRate: WS_REALTIME_SAMPLE_RATE });
32
+ this.audioContext = ctx;
33
+ this.source = ctx.createMediaStreamSource(this.stream);
34
+ this.processor = ctx.createScriptProcessor(4096, 1, 1);
35
+ this.silentGain = ctx.createGain();
36
+ this.silentGain.gain.value = 0;
37
+ this.source.connect(this.processor);
38
+ this.processor.connect(this.silentGain);
39
+ this.silentGain.connect(ctx.destination);
40
+ this.processor.onaudioprocess = (event) => {
41
+ if (!this.session?.appendInputAudio)
42
+ return;
43
+ const channel = event.inputBuffer.getChannelData(0);
44
+ const rms = computeRms(channel);
45
+ this.onRms?.(rms);
46
+ this.session.appendInputAudio(float32ToPcm16(channel));
47
+ };
48
+ if (ctx.state === 'suspended') {
49
+ await ctx.resume();
50
+ }
51
+ }
52
+ stop() {
53
+ this.processor?.disconnect();
54
+ this.source?.disconnect();
55
+ this.silentGain?.disconnect();
56
+ this.processor = null;
57
+ this.source = null;
58
+ this.silentGain = null;
59
+ for (const track of this.stream?.getTracks() ?? []) {
60
+ track.stop();
61
+ }
62
+ this.stream = null;
63
+ void this.audioContext?.close();
64
+ this.audioContext = null;
65
+ this.session?.clearInputAudio?.();
66
+ }
67
+ cleanup() {
68
+ this.stop();
69
+ this.session = null;
70
+ }
71
+ }
@@ -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/ws/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,5 @@
1
+ export declare const MIC_SPEECH_RMS_THRESHOLD = 0.008;
2
+ export declare const MIC_BARGE_IN_RMS_THRESHOLD = 0.045;
3
+ export declare const MIC_SILENCE_COMMIT_MS = 400;
4
+ export declare const MIC_SILENCE_POLL_MS = 100;
5
+ //# sourceMappingURL=voiceSilenceConstants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceSilenceConstants.d.ts","sourceRoot":"","sources":["../../../src/voice/ws/voiceSilenceConstants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAC9C,eAAO,MAAM,0BAA0B,QAAQ,CAAC;AAChD,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,mBAAmB,MAAM,CAAC"}
@@ -0,0 +1,4 @@
1
+ export const MIC_SPEECH_RMS_THRESHOLD = 0.008;
2
+ export const MIC_BARGE_IN_RMS_THRESHOLD = 0.045;
3
+ export const MIC_SILENCE_COMMIT_MS = 400;
4
+ export const MIC_SILENCE_POLL_MS = 100;
@@ -0,0 +1,2 @@
1
+ export declare const WS_REALTIME_SAMPLE_RATE = 24000;
2
+ //# sourceMappingURL=wsRealtimeConstants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsRealtimeConstants.d.ts","sourceRoot":"","sources":["../../../src/voice/ws/wsRealtimeConstants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,QAAQ,CAAC"}
@@ -0,0 +1 @@
1
+ export const WS_REALTIME_SAMPLE_RATE = 24000;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@bytexbyte/nxtlinq-ai-agent-ui-react-development",
3
+ "version": "0.1.1",
4
+ "description": "Official React Web UI for nxtlinq AI Agent — drop-in chat widget",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "src"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "clean": "rm -rf dist",
14
+ "prepublishOnly": "yarn build"
15
+ },
16
+ "keywords": [
17
+ "nxtlinq",
18
+ "ai-agent",
19
+ "react",
20
+ "web",
21
+ "ui",
22
+ "chatbot"
23
+ ],
24
+ "author": "ByteXByte",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://dev.azure.com/nxtlinqLLC/nxtlinq/_git/nxtlinq-AI-Agent-SDK",
29
+ "directory": "packages/ai-agent-ui-react"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "sideEffects": false,
36
+ "peerDependencies": {
37
+ "react": ">=18.0.0",
38
+ "react-dom": ">=18.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@bytexbyte/nxtlinq-ai-agent-core-development": "0.3.8",
42
+ "@bytexbyte/nxtlinq-ai-agent-web-development": "0.1.1",
43
+ "@emotion/react": "^11.14.0",
44
+ "@emotion/styled": "^11.14.1",
45
+ "@mui/icons-material": "^7.2.0",
46
+ "@mui/material": "^7.2.0",
47
+ "ethers": "^6.16.0",
48
+ "fast-json-stable-stringify": "^2.1.0",
49
+ "uuid": "^11.1.0"
50
+ },
51
+ "devDependencies": {
52
+ "@bytexbyte/nxtlinq-ai-agent-core-development": "workspace:^",
53
+ "@bytexbyte/nxtlinq-ai-agent-web-development": "workspace:^",
54
+ "@types/react": "^18.2.64",
55
+ "@types/react-dom": "^18.2.25",
56
+ "react": "^18.2.0",
57
+ "react-dom": "^18.2.0",
58
+ "typescript": "^5.4.2"
59
+ }
60
+ }
@@ -0,0 +1,79 @@
1
+ import { NxtlinqAgentProvider } from '@bytexbyte/nxtlinq-ai-agent-web-development';
2
+ import React from 'react';
3
+ import { AgentAssistantShell } from './components/AgentAssistantShell';
4
+ import type { NxtlinqAgentChatProps } from './types';
5
+
6
+ /**
7
+ * Drop-in React Web assistant UI wired to `@bytexbyte/nxtlinq-ai-agent-web-development`.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { NxtlinqAgentChat } from '@bytexbyte/nxtlinq-ai-agent-ui-react-development';
12
+ *
13
+ * export default function Page() {
14
+ * return (
15
+ * <NxtlinqAgentChat
16
+ * style={{ height: '100vh' }}
17
+ * serviceId="..."
18
+ * apiKey="..."
19
+ * apiSecret="..."
20
+ * environment="staging"
21
+ * pseudoId={userId}
22
+ * loadHistoryOnMount
23
+ * />
24
+ * );
25
+ * }
26
+ * ```
27
+ */
28
+ export function NxtlinqAgentChat({
29
+ title,
30
+ placeholder,
31
+ presetMessages,
32
+ loadHistoryOnMount = false,
33
+ historyLast,
34
+ enableVoice = true,
35
+ enableFileUpload = true,
36
+ startInVoiceMode = false,
37
+ startWithMicMuted = true,
38
+ holdMicDuringAssistant = true,
39
+ theme,
40
+ style,
41
+ headerStyle,
42
+ onMessage,
43
+ onError,
44
+ onToolUse,
45
+ children,
46
+ fetchImpl,
47
+ getTimezone,
48
+ resetOnIdentityChange,
49
+ ...agentConfig
50
+ }: NxtlinqAgentChatProps): React.ReactElement {
51
+ return (
52
+ <NxtlinqAgentProvider
53
+ fetchImpl={fetchImpl}
54
+ getTimezone={getTimezone}
55
+ resetOnIdentityChange={resetOnIdentityChange}
56
+ onMessage={onMessage}
57
+ onError={onError}
58
+ onToolUse={onToolUse}
59
+ {...agentConfig}
60
+ >
61
+ <AgentAssistantShell
62
+ title={title}
63
+ placeholder={placeholder}
64
+ presetMessages={presetMessages}
65
+ loadHistoryOnMount={loadHistoryOnMount}
66
+ historyLast={historyLast}
67
+ enableVoice={enableVoice}
68
+ enableFileUpload={enableFileUpload}
69
+ startInVoiceMode={startInVoiceMode}
70
+ startWithMicMuted={startWithMicMuted}
71
+ holdMicDuringAssistant={holdMicDuringAssistant}
72
+ theme={theme}
73
+ style={style}
74
+ headerStyle={headerStyle}
75
+ />
76
+ {children}
77
+ </NxtlinqAgentProvider>
78
+ );
79
+ }
@@ -0,0 +1,104 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import {
3
+ AgentAssistantProvider,
4
+ useAgentAssistant,
5
+ } from '../context/AgentAssistantContext';
6
+ import type { NxtlinqAgentChatProps } from '../types';
7
+ import { AgentComposer } from './AgentComposer';
8
+ import { AgentMessageList } from './AgentMessageList';
9
+ import { AgentRemoteAudio } from './AgentRemoteAudio';
10
+ import { AgentVoiceBar } from './AgentVoiceBar';
11
+ import { PresetMessageChips } from './PresetMessageChips';
12
+
13
+ export type AgentAssistantShellProps = Pick<
14
+ NxtlinqAgentChatProps,
15
+ | 'title'
16
+ | 'placeholder'
17
+ | 'presetMessages'
18
+ | 'enableVoice'
19
+ | 'enableFileUpload'
20
+ | 'theme'
21
+ | 'style'
22
+ | 'headerStyle'
23
+ | 'loadHistoryOnMount'
24
+ | 'historyLast'
25
+ | 'startInVoiceMode'
26
+ | 'startWithMicMuted'
27
+ | 'holdMicDuringAssistant'
28
+ >;
29
+
30
+ function AgentAssistantInner({
31
+ title,
32
+ headerStyle,
33
+ style,
34
+ loadHistoryOnMount,
35
+ historyLast,
36
+ startInVoiceMode,
37
+ }: AgentAssistantShellProps): React.ReactElement {
38
+ const { theme, loadHistory, startVoice, isVoiceAvailable } = useAgentAssistant();
39
+ const [historyReady, setHistoryReady] = useState(!loadHistoryOnMount);
40
+ const voiceAutoStartRef = useRef(false);
41
+
42
+ useEffect(() => {
43
+ if (loadHistoryOnMount) {
44
+ void loadHistory({ last: historyLast ?? 50 }).finally(() => setHistoryReady(true));
45
+ }
46
+ }, [loadHistory, loadHistoryOnMount, historyLast]);
47
+
48
+ useEffect(() => {
49
+ if (!startInVoiceMode || !isVoiceAvailable || voiceAutoStartRef.current) return;
50
+ voiceAutoStartRef.current = true;
51
+ void startVoice();
52
+ }, [startInVoiceMode, isVoiceAvailable, startVoice]);
53
+
54
+ return (
55
+ <div
56
+ style={{
57
+ display: 'flex',
58
+ flexDirection: 'column',
59
+ flex: 1,
60
+ minHeight: 0,
61
+ backgroundColor: theme.colors.background,
62
+ ...style,
63
+ }}
64
+ >
65
+ <header
66
+ style={{
67
+ padding: theme.spacing.md,
68
+ borderBottom: `1px solid ${theme.colors.border}`,
69
+ backgroundColor: theme.colors.surface,
70
+ fontSize: theme.typography.titleSize,
71
+ fontWeight: 600,
72
+ color: theme.colors.assistantText,
73
+ ...headerStyle,
74
+ }}
75
+ >
76
+ {title ?? 'AI Assistant'}
77
+ </header>
78
+ <PresetMessageChips />
79
+ {historyReady ? <AgentMessageList /> : <div style={{ flex: 1 }} />}
80
+ <AgentVoiceBar />
81
+ <AgentComposer />
82
+ <AgentRemoteAudio />
83
+ </div>
84
+ );
85
+ }
86
+
87
+ export function AgentAssistantShell(props: AgentAssistantShellProps): React.ReactElement {
88
+ return (
89
+ <AgentAssistantProvider
90
+ ui={{
91
+ title: props.title,
92
+ placeholder: props.placeholder,
93
+ presetMessages: props.presetMessages,
94
+ enableVoice: props.enableVoice,
95
+ enableFileUpload: props.enableFileUpload,
96
+ theme: props.theme,
97
+ startWithMicMuted: props.startWithMicMuted,
98
+ holdMicDuringAssistant: props.holdMicDuringAssistant,
99
+ }}
100
+ >
101
+ <AgentAssistantInner {...props} />
102
+ </AgentAssistantProvider>
103
+ );
104
+ }