reactbridge-sdk 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,6 +82,8 @@ Wraps your application and provides configuration.
82
82
  | `apiKey` | `string` | Yes | Your ReactBridge API key |
83
83
  | `config` | `object` | No | Additional configuration (baseURL, timeout, headers) |
84
84
  | `theme` | `Theme` | No | Custom theme object |
85
+ | `sttProvider` | `STTProvider` | No | Custom speech-to-text provider |
86
+ | `ttsProvider` | `TTSProvider` | No | Custom text-to-speech provider |
85
87
 
86
88
  **Example:**
87
89
 
@@ -114,6 +116,10 @@ Full-featured chat interface component.
114
116
  | `theme` | `Partial<Theme>` | No | Theme overrides |
115
117
  | `renderMessage` | `function` | No | Custom message renderer |
116
118
  | `onError` | `function` | No | Error handler |
119
+ | `onSpeechStart` | `function` | No | Called when voice input starts |
120
+ | `onSpeechEnd` | `function` | No | Called when voice input ends |
121
+ | `onTranscript` | `function` | No | Called with transcribed text |
122
+ | `onAgentResponse` | `function` | No | Called with agent response text |
117
123
 
118
124
  ### `<ReactBridgeSearch />`
119
125
 
@@ -130,6 +136,10 @@ Compact search bar component.
130
136
  | `maxResults` | `number` | No | Maximum results to show (default: 5) |
131
137
  | `theme` | `Partial<Theme>` | No | Theme overrides |
132
138
  | `onError` | `function` | No | Error handler |
139
+ | `onSpeechStart` | `function` | No | Called when voice input starts |
140
+ | `onSpeechEnd` | `function` | No | Called when voice input ends |
141
+ | `onTranscript` | `function` | No | Called with transcribed text |
142
+ | `onAgentResponse` | `function` | No | Called with agent response text |
133
143
 
134
144
  ### `useReactBridge(options)`
135
145
 
@@ -177,6 +187,72 @@ function CustomChat() {
177
187
  }
178
188
  ```
179
189
 
190
+ ## Voice Input/Output
191
+
192
+ The SDK supports voice input (Speech-to-Text) and voice output (Text-to-Speech) for a fully conversational experience.
193
+
194
+ ### Default Providers
195
+
196
+ By default, the SDK uses browser Web Speech APIs:
197
+ - **STT**: `WebSpeechSTTProvider` (SpeechRecognition)
198
+ - **TTS**: `WebSpeechTTSProvider` (speechSynthesis)
199
+
200
+ ### Custom Providers
201
+
202
+ You can swap providers for advanced implementations:
203
+
204
+ ```tsx
205
+ import { ReactBridgeProvider, WebSpeechSTTProvider, WebSpeechTTSProvider } from 'reactbridge-sdk';
206
+
207
+ // Custom STT Provider
208
+ class MySTTProvider {
209
+ startRecognition() { /* ... */ }
210
+ stopRecognition() { /* ... */ }
211
+ onResult(callback: (text: string) => void) { /* ... */ }
212
+ }
213
+
214
+ // Custom TTS Provider
215
+ class MyTTSProvider {
216
+ speak(text: string) { /* ... */ }
217
+ }
218
+
219
+ <ReactBridgeProvider
220
+ apiKey="your-key"
221
+ sttProvider={new MySTTProvider()}
222
+ ttsProvider={new MyTTSProvider()}
223
+ >
224
+ <App />
225
+ </ReactBridgeProvider>
226
+ ```
227
+
228
+ ### Voice Events
229
+
230
+ ```tsx
231
+ const { startVoiceInput, stopVoiceInput, isListening } = useReactBridge({
232
+ onIntentDetected: handleAction,
233
+ currentContext: context,
234
+ onSpeechStart: () => console.log('Voice input started'),
235
+ onSpeechEnd: () => console.log('Voice input ended'),
236
+ onTranscript: (text) => console.log('Transcribed:', text),
237
+ onAgentResponse: (response) => console.log('Agent responded:', response),
238
+ });
239
+ ```
240
+
241
+ ### Chatbox with Voice
242
+
243
+ The `ReactBridgeChatbox` includes a microphone button next to the text input:
244
+
245
+ ```tsx
246
+ <ReactBridgeChatbox
247
+ onIntentDetected={handleAction}
248
+ currentContext={context}
249
+ />
250
+ ```
251
+
252
+ - Click the mic button to start/stop voice recording
253
+ - Transcribed text is automatically sent to the API
254
+ - Agent responses are spoken aloud
255
+
180
256
  ## Theming
181
257
 
182
258
  ### Built-in Themes
@@ -9,6 +9,10 @@ export interface ReactBridgeChatboxProps {
9
9
  theme?: Partial<Theme>;
10
10
  renderMessage?: (message: ChatMessage) => React.ReactNode;
11
11
  onError?: (error: Error) => void;
12
+ onSpeechStart?: () => void;
13
+ onSpeechEnd?: () => void;
14
+ onTranscript?: (text: string) => void;
15
+ onAgentResponse?: (response: string) => void;
12
16
  renderMode?: "standard" | "basic";
13
17
  defaultOpen?: boolean;
14
18
  boxLocation?: "bottom-right" | "bottom-left";
@@ -20,7 +24,7 @@ export interface ReactBridgeChatboxProps {
20
24
  toggleButtonClass?: string;
21
25
  toggleButtonTitle?: string;
22
26
  }
23
- export declare function ReactBridgeChatbox({ onIntentDetected, currentContext, placeholder, height, width, theme: themeOverride, renderMessage, onError, renderMode, // Default to 'basic' (original behavior)
27
+ export declare function ReactBridgeChatbox({ onIntentDetected, currentContext, placeholder, height, width, theme: themeOverride, renderMessage, onError, onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse, renderMode, // Default to 'basic' (original behavior)
24
28
  defaultOpen, boxLocation, titleText, titleIcon, titleTextColor, // Will use theme.colors.text or default white based on headerBgColor
25
29
  headerBgColor, // Will use theme.colors.primary if not set
26
30
  toggleIcon, // <<< NOW USES THE PURE SVG CONSTANT
@@ -1 +1 @@
1
- {"version":3,"file":"ReactBridgeChatbox.d.ts","sourceRoot":"","sources":["../../src/components/ReactBridgeChatbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAI3D,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,KAAK,EACL,cAAc,EACf,MAAM,UAAU,CAAC;AAqBlB,MAAM,WAAW,uBAAuB;IAEtC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAGjC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC;IAG7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,gBAAgB,EAChB,cAAc,EACd,WAAoC,EACpC,MAAgB,EAChB,KAAc,EACd,KAAK,EAAE,aAAa,EACpB,aAAa,EACb,OAAO,EAGP,UAAoB,EAAE,yCAAyC;AAC/D,WAAmB,EACnB,WAA4B,EAC5B,SAA0B,EAC1B,SAAgB,EAChB,cAAc,EAAE,qEAAqE;AACrF,aAAa,EAAE,2CAA2C;AAE1D,UAA6B,EAAE,qCAAqC;AACpE,iBAA4C,EAC5C,iBAAyC,GAC1C,EAAE,uBAAuB,qBA0TzB"}
1
+ {"version":3,"file":"ReactBridgeChatbox.d.ts","sourceRoot":"","sources":["../../src/components/ReactBridgeChatbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAI3D,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,KAAK,EACL,cAAc,EACf,MAAM,UAAU,CAAC;AAkClB,MAAM,WAAW,uBAAuB;IAEtC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAGjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAG7C,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC;IAG7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,gBAAgB,EAChB,cAAc,EACd,WAAoC,EACpC,MAAgB,EAChB,KAAc,EACd,KAAK,EAAE,aAAa,EACpB,aAAa,EACb,OAAO,EAGP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,EAGf,UAAoB,EAAE,yCAAyC;AAC/D,WAAmB,EACnB,WAA4B,EAC5B,SAA0B,EAC1B,SAAgB,EAChB,cAAc,EAAE,qEAAqE;AACrF,aAAa,EAAE,2CAA2C;AAE1D,UAA6B,EAAE,qCAAqC;AACpE,iBAA4C,EAC5C,iBAAyC,GAC1C,EAAE,uBAAuB,qBA0WzB"}
@@ -8,6 +8,10 @@ export interface ReactBridgeSearchProps {
8
8
  maxResults?: number;
9
9
  theme?: Partial<Theme>;
10
10
  onError?: (error: Error) => void;
11
+ onSpeechStart?: () => void;
12
+ onSpeechEnd?: () => void;
13
+ onTranscript?: (text: string) => void;
14
+ onAgentResponse?: (response: string) => void;
11
15
  }
12
- export declare function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder, width, maxResults, theme: themeOverride, onError, }: ReactBridgeSearchProps): React.JSX.Element;
16
+ export declare function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder, width, maxResults, theme: themeOverride, onError, onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse, }: ReactBridgeSearchProps): React.JSX.Element;
13
17
  //# sourceMappingURL=ReactBridgeSearch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactBridgeSearch.d.ts","sourceRoot":"","sources":["../../src/components/ReactBridgeSearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,OAAO,KAAK,EAAE,gBAAgB,EAAkB,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAExF,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,gBAAgB,EAChB,cAAc,EACd,WAAyB,EACzB,KAAc,EACd,UAAc,EACd,KAAK,EAAE,aAAa,EACpB,OAAO,GACR,EAAE,sBAAsB,qBA+IxB"}
1
+ {"version":3,"file":"ReactBridgeSearch.d.ts","sourceRoot":"","sources":["../../src/components/ReactBridgeSearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,OAAO,KAAK,EAAE,gBAAgB,EAAkB,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAexF,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED,wBAAgB,iBAAiB,CAAC,EAChC,gBAAgB,EAChB,cAAc,EACd,WAAyB,EACzB,KAAc,EACd,UAAc,EACd,KAAK,EAAE,aAAa,EACpB,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,EAAE,sBAAsB,qBA6KxB"}
@@ -1,3 +1,3 @@
1
1
  import type { UseReactBridgeOptions, UseReactBridgeReturn } from '../types';
2
- export declare function useReactBridge({ onIntentDetected, currentContext, onError, }: UseReactBridgeOptions): UseReactBridgeReturn;
2
+ export declare function useReactBridge({ onIntentDetected, currentContext, onError, onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse, }: UseReactBridgeOptions): UseReactBridgeReturn;
3
3
  //# sourceMappingURL=useReactBridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useReactBridge.d.ts","sourceRoot":"","sources":["../../src/hooks/useReactBridge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,qBAAqB,EACrB,oBAAoB,EAIrB,MAAM,UAAU,CAAC;AAElB,wBAAgB,cAAc,CAAC,EAC7B,gBAAgB,EAChB,cAAc,EACd,OAAO,GACR,EAAE,qBAAqB,GAAG,oBAAoB,CAyJ9C"}
1
+ {"version":3,"file":"useReactBridge.d.ts","sourceRoot":"","sources":["../../src/hooks/useReactBridge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,qBAAqB,EACrB,oBAAoB,EAIrB,MAAM,UAAU,CAAC;AAElB,wBAAgB,cAAc,CAAC,EAC7B,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,EAAE,qBAAqB,GAAG,oBAAoB,CA8M9C"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@ export { ReactBridgeChatbox } from './components/ReactBridgeChatbox';
5
5
  export type { ReactBridgeChatboxProps } from './components/ReactBridgeChatbox';
6
6
  export { ReactBridgeSearch } from './components/ReactBridgeSearch';
7
7
  export type { ReactBridgeSearchProps } from './components/ReactBridgeSearch';
8
- export type { ReactBridgeConfig, ChatMessage, ToolCall, ToolResult, InterfaceState, CurrentContext, ViewingItem, OnIntentDetected, UseReactBridgeOptions, UseReactBridgeReturn, Theme, ThemeColors, ThemeSpacing, ThemeFontSizes, ThemeMode, } from './types';
8
+ export type { ReactBridgeConfig, ChatMessage, ToolCall, ToolResult, InterfaceState, CurrentContext, ViewingItem, OnIntentDetected, UseReactBridgeOptions, UseReactBridgeReturn, Theme, ThemeColors, ThemeSpacing, ThemeFontSizes, ThemeMode, STTProvider, TTSProvider, } from './types';
9
9
  export { lightTheme, darkTheme, getTheme, createCustomTheme } from './themes';
10
10
  export { ReactBridgeAPI } from './utils/api';
11
+ export { WebSpeechSTTProvider, WebSpeechTTSProvider } from './utils/voice';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,YAAY,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,YAAY,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,GACZ,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useState, useRef, useEffect, useCallback } from 'react';
1
+ import React, { createContext, useState, useContext, useRef, useEffect, useCallback } from 'react';
2
2
 
3
3
  /******************************************************************************
4
4
  Copyright (c) Microsoft Corporation.
@@ -124,8 +124,73 @@ const lightTheme = {
124
124
  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
125
125
  };
126
126
 
127
+ // Default STT Provider using Web Speech API
128
+ class WebSpeechSTTProvider {
129
+ constructor() {
130
+ if (typeof window !== 'undefined') {
131
+ const SpeechRecognitionCtor = window.SpeechRecognition || window.webkitSpeechRecognition;
132
+ if (SpeechRecognitionCtor) {
133
+ this.recognition = new SpeechRecognitionCtor();
134
+ if (!this.recognition) {
135
+ throw new Error('Failed to initialize SpeechRecognition');
136
+ }
137
+ this.recognition.continuous = false;
138
+ this.recognition.interimResults = false;
139
+ this.recognition.lang = 'en-US';
140
+ this.recognition.onresult = (event) => {
141
+ var _a;
142
+ const transcript = event.results[0][0].transcript;
143
+ (_a = this.onResultCallback) === null || _a === void 0 ? void 0 : _a.call(this, transcript);
144
+ };
145
+ this.recognition.onerror = (event) => {
146
+ var _a;
147
+ (_a = this.onErrorCallback) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Speech recognition error: ${event.error}`));
148
+ };
149
+ }
150
+ else {
151
+ console.warn('SpeechRecognition API not supported in this browser.');
152
+ }
153
+ }
154
+ }
155
+ startRecognition() {
156
+ if (this.recognition) {
157
+ this.recognition.start();
158
+ }
159
+ else {
160
+ throw new Error('Speech recognition not supported');
161
+ }
162
+ }
163
+ stopRecognition() {
164
+ var _a;
165
+ (_a = this.recognition) === null || _a === void 0 ? void 0 : _a.stop();
166
+ }
167
+ onResult(callback) {
168
+ this.onResultCallback = callback;
169
+ }
170
+ onError(callback) {
171
+ this.onErrorCallback = callback;
172
+ }
173
+ }
174
+ // Default TTS Provider using Web Speech API
175
+ class WebSpeechTTSProvider {
176
+ speak(text) {
177
+ if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
178
+ const utterance = new SpeechSynthesisUtterance(text);
179
+ window.speechSynthesis.speak(utterance);
180
+ }
181
+ else {
182
+ console.warn('Text-to-speech not supported');
183
+ }
184
+ }
185
+ stop() {
186
+ if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
187
+ window.speechSynthesis.cancel();
188
+ }
189
+ }
190
+ }
191
+
127
192
  const ReactBridgeContext = createContext(null);
128
- function ReactBridgeProvider({ apiKey, config = {}, theme = lightTheme, children, }) {
193
+ function ReactBridgeProvider({ apiKey, config = {}, theme = lightTheme, sttProvider: initialSTTProvider, ttsProvider: initialTTSProvider, children, }) {
129
194
  const fullConfig = {
130
195
  apiKey,
131
196
  baseURL: config.baseURL,
@@ -138,10 +203,16 @@ function ReactBridgeProvider({ apiKey, config = {}, theme = lightTheme, children
138
203
  fullConfig.baseURL,
139
204
  fullConfig.timeout,
140
205
  ]);
206
+ const [sttProvider, setSTTProvider] = useState(initialSTTProvider || new WebSpeechSTTProvider());
207
+ const [ttsProvider, setTTSProvider] = useState(initialTTSProvider || new WebSpeechTTSProvider());
141
208
  const value = {
142
209
  api,
143
210
  config: fullConfig,
144
211
  theme,
212
+ sttProvider,
213
+ ttsProvider,
214
+ setSTTProvider,
215
+ setTTSProvider,
145
216
  };
146
217
  return (React.createElement(ReactBridgeContext.Provider, { value: value }, children));
147
218
  }
@@ -187,12 +258,13 @@ function getContextSummary(context) {
187
258
  return parts.join('\n');
188
259
  }
189
260
 
190
- function useReactBridge({ onIntentDetected, currentContext, onError, }) {
191
- const { api } = useReactBridgeContext();
261
+ function useReactBridge({ onIntentDetected, currentContext, onError, onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse, }) {
262
+ const { api, sttProvider, ttsProvider } = useReactBridgeContext();
192
263
  const [messages, setMessages] = useState([]);
193
264
  const [isLoading, setIsLoading] = useState(false);
194
265
  const [error, setError] = useState(null);
195
266
  const [sessionId, setSessionId] = useState(null);
267
+ const [isListening, setIsListening] = useState(false);
196
268
  const previousContextRef = useRef(null);
197
269
  const lastRequestRef = useRef(null);
198
270
  // Inject context changes as system messages
@@ -252,6 +324,11 @@ function useReactBridge({ onIntentDetected, currentContext, onError, }) {
252
324
  toolCall: response.toolCall,
253
325
  };
254
326
  setMessages(prev => [...prev, assistantMessage]);
327
+ // Trigger TTS and callback
328
+ ttsProvider.speak(response.message);
329
+ if (onAgentResponse) {
330
+ onAgentResponse(response.message);
331
+ }
255
332
  // Step 2: If there's a tool call, execute it
256
333
  if (response.toolCall && onIntentDetected) {
257
334
  try {
@@ -267,6 +344,11 @@ function useReactBridge({ onIntentDetected, currentContext, onError, }) {
267
344
  timestamp: new Date(),
268
345
  };
269
346
  setMessages(prev => [...prev, finalMessage]);
347
+ // Trigger TTS for final response
348
+ ttsProvider.speak(resultResponse.message);
349
+ if (onAgentResponse) {
350
+ onAgentResponse(resultResponse.message);
351
+ }
270
352
  }
271
353
  }
272
354
  catch (toolError) {
@@ -309,12 +391,50 @@ function useReactBridge({ onIntentDetected, currentContext, onError, }) {
309
391
  setSessionId(null);
310
392
  setError(null);
311
393
  }, []);
394
+ const startVoiceInput = useCallback(() => {
395
+ setIsListening(true);
396
+ if (onSpeechStart) {
397
+ onSpeechStart();
398
+ }
399
+ sttProvider.onResult((text) => {
400
+ if (onTranscript) {
401
+ onTranscript(text);
402
+ }
403
+ sendChatQuery(text);
404
+ setIsListening(false);
405
+ if (onSpeechEnd) {
406
+ onSpeechEnd();
407
+ }
408
+ });
409
+ if (sttProvider.onError) {
410
+ sttProvider.onError((error) => {
411
+ setIsListening(false);
412
+ if (onError) {
413
+ onError(error);
414
+ }
415
+ if (onSpeechEnd) {
416
+ onSpeechEnd();
417
+ }
418
+ });
419
+ }
420
+ sttProvider.startRecognition();
421
+ }, [sttProvider, sendChatQuery, onSpeechStart, onSpeechEnd, onTranscript, onError]);
422
+ const stopVoiceInput = useCallback(() => {
423
+ setIsListening(false);
424
+ sttProvider.stopRecognition();
425
+ if (onSpeechEnd) {
426
+ onSpeechEnd();
427
+ }
428
+ }, [sttProvider, onSpeechEnd]);
312
429
  return {
313
430
  messages,
314
431
  isLoading,
315
432
  sendChatQuery,
316
433
  clearMessages,
317
434
  error,
435
+ isListening,
436
+ startVoiceInput,
437
+ stopVoiceInput,
318
438
  };
319
439
  }
320
440
 
@@ -323,9 +443,14 @@ const MESSAGE_ICON_SVG = (React.createElement("svg", { xmlns: "http://www.w3.org
323
443
  , height: "1em", fill: "currentColor" },
324
444
  React.createElement("path", { d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z" })));
325
445
  // --- END: Dependency-Free SVG Message Icon ---
446
+ // Microphone Icon SVG
447
+ const MIC_ICON_SVG$1 = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "currentColor" },
448
+ React.createElement("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" })));
326
449
  // Default styling constants for the widget wrapper
327
450
  const defaultToggleButtonClass = "fixed bottom-6 right-6 z-40 w-14 h-14 rounded-full shadow-lg text-white bg-blue-600 hover:bg-blue-700 transition-all flex justify-center items-center cursor-pointer text-2xl";
328
451
  function ReactBridgeChatbox({ onIntentDetected, currentContext, placeholder = "Type your message...", height = "500px", width = "100%", theme: themeOverride, renderMessage, onError,
452
+ // Voice Event Props
453
+ onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse,
329
454
  // NEW Widget Props
330
455
  renderMode = "basic", // Default to 'basic' (original behavior)
331
456
  defaultOpen = false, boxLocation = "bottom-right", titleText = "AI Assistant", titleIcon = null, titleTextColor, // Will use theme.colors.text or default white based on headerBgColor
@@ -334,10 +459,14 @@ toggleIcon = MESSAGE_ICON_SVG, // <<< NOW USES THE PURE SVG CONSTANT
334
459
  toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat assistant", }) {
335
460
  const { theme: contextTheme } = useReactBridgeContext();
336
461
  const theme = Object.assign(Object.assign({}, contextTheme), themeOverride);
337
- const { messages, isLoading, sendChatQuery } = useReactBridge({
462
+ const { messages, isLoading, sendChatQuery, isListening, startVoiceInput, stopVoiceInput } = useReactBridge({
338
463
  onIntentDetected,
339
464
  currentContext,
340
465
  onError,
466
+ onSpeechStart,
467
+ onSpeechEnd,
468
+ onTranscript,
469
+ onAgentResponse,
341
470
  });
342
471
  const [inputValue, setInputValue] = useState("");
343
472
  // New: Manage widget open/closed state
@@ -408,7 +537,7 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
408
537
  borderTop: `1px solid ${theme.colors.border}`,
409
538
  backgroundColor: theme.colors.surface,
410
539
  } },
411
- React.createElement("div", { style: { display: "flex", gap: theme.spacing.sm } },
540
+ React.createElement("div", { style: { display: "flex", gap: theme.spacing.sm, alignItems: "center" } },
412
541
  React.createElement("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: placeholder, disabled: isLoading, style: {
413
542
  flex: 1,
414
543
  padding: theme.spacing.sm,
@@ -419,6 +548,20 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
419
548
  color: theme.colors.text,
420
549
  outline: "none",
421
550
  } }),
551
+ React.createElement("button", { type: "button", onClick: isListening ? stopVoiceInput : startVoiceInput, disabled: isLoading, title: isListening ? "Stop recording" : "Start voice input", style: {
552
+ padding: theme.spacing.sm,
553
+ backgroundColor: isListening ? theme.colors.error : theme.colors.secondary,
554
+ color: "#ffffff",
555
+ border: "none",
556
+ borderRadius: theme.borderRadius,
557
+ cursor: isLoading ? "not-allowed" : "pointer",
558
+ opacity: isLoading ? 0.5 : 1,
559
+ display: "flex",
560
+ alignItems: "center",
561
+ justifyContent: "center",
562
+ width: "40px",
563
+ height: "40px",
564
+ } }, MIC_ICON_SVG$1),
422
565
  React.createElement("button", { type: "submit", disabled: isLoading || !inputValue.trim(), style: {
423
566
  padding: `${theme.spacing.sm} ${theme.spacing.md}`,
424
567
  fontSize: theme.fontSizes.md,
@@ -487,7 +630,7 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
487
630
  borderTop: `1px solid ${theme.colors.border}`,
488
631
  backgroundColor: theme.colors.surface,
489
632
  } },
490
- React.createElement("div", { style: { display: "flex", gap: theme.spacing.sm } },
633
+ React.createElement("div", { style: { display: "flex", gap: theme.spacing.sm, alignItems: "center" } },
491
634
  React.createElement("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: placeholder, disabled: isLoading, style: {
492
635
  flex: 1,
493
636
  padding: theme.spacing.sm,
@@ -498,6 +641,20 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
498
641
  color: theme.colors.text,
499
642
  outline: "none",
500
643
  } }),
644
+ React.createElement("button", { type: "button", onClick: isListening ? stopVoiceInput : startVoiceInput, disabled: isLoading, title: isListening ? "Stop recording" : "Start voice input", style: {
645
+ padding: theme.spacing.sm,
646
+ backgroundColor: isListening ? theme.colors.error : theme.colors.secondary,
647
+ color: "#ffffff",
648
+ border: "none",
649
+ borderRadius: theme.borderRadius,
650
+ cursor: isLoading ? "not-allowed" : "pointer",
651
+ opacity: isLoading ? 0.5 : 1,
652
+ display: "flex",
653
+ alignItems: "center",
654
+ justifyContent: "center",
655
+ width: "40px",
656
+ height: "40px",
657
+ } }, MIC_ICON_SVG$1),
501
658
  React.createElement("button", { type: "submit", disabled: isLoading || !inputValue.trim(), style: {
502
659
  padding: `${theme.spacing.sm} ${theme.spacing.md}`,
503
660
  fontSize: theme.fontSizes.md,
@@ -510,13 +667,20 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
510
667
  } }, isLoading ? "Sending..." : "Send")))));
511
668
  }
512
669
 
513
- function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder = 'Search...', width = '100%', maxResults = 5, theme: themeOverride, onError, }) {
670
+ // Microphone Icon SVG
671
+ const MIC_ICON_SVG = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "1em", height: "1em", fill: "currentColor" },
672
+ React.createElement("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" })));
673
+ function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder = 'Search...', width = '100%', maxResults = 5, theme: themeOverride, onError, onSpeechStart, onSpeechEnd, onTranscript, onAgentResponse, }) {
514
674
  const { theme: contextTheme } = useReactBridgeContext();
515
675
  const theme = Object.assign(Object.assign({}, contextTheme), themeOverride);
516
- const { messages, isLoading, sendChatQuery } = useReactBridge({
676
+ const { messages, isLoading, sendChatQuery, isListening, startVoiceInput, stopVoiceInput } = useReactBridge({
517
677
  onIntentDetected,
518
678
  currentContext,
519
679
  onError,
680
+ onSpeechStart,
681
+ onSpeechEnd,
682
+ onTranscript,
683
+ onAgentResponse,
520
684
  });
521
685
  const [inputValue, setInputValue] = useState('');
522
686
  const [isOpen, setIsOpen] = useState(false);
@@ -558,7 +722,7 @@ function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder = 'Se
558
722
  React.createElement("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), onFocus: () => displayMessages.length > 0 && setIsOpen(true), placeholder: placeholder, disabled: isLoading, style: {
559
723
  width: '100%',
560
724
  padding: theme.spacing.md,
561
- paddingRight: '100px',
725
+ paddingRight: '140px', // Increased to make room for both mic and search buttons
562
726
  fontSize: theme.fontSizes.md,
563
727
  border: `1px solid ${theme.colors.border}`,
564
728
  borderRadius: theme.borderRadius,
@@ -567,6 +731,24 @@ function ReactBridgeSearch({ onIntentDetected, currentContext, placeholder = 'Se
567
731
  outline: 'none',
568
732
  boxSizing: 'border-box',
569
733
  } }),
734
+ React.createElement("button", { type: "button", onClick: isListening ? stopVoiceInput : startVoiceInput, disabled: isLoading, title: isListening ? "Stop recording" : "Start voice input", style: {
735
+ position: 'absolute',
736
+ right: '70px', // Position before the search button
737
+ top: '50%',
738
+ transform: 'translateY(-50%)',
739
+ padding: theme.spacing.sm,
740
+ backgroundColor: isListening ? theme.colors.error : theme.colors.secondary,
741
+ color: "#ffffff",
742
+ border: "none",
743
+ borderRadius: theme.borderRadius,
744
+ cursor: isLoading ? "not-allowed" : "pointer",
745
+ opacity: isLoading ? 0.5 : 1,
746
+ display: "flex",
747
+ alignItems: "center",
748
+ justifyContent: "center",
749
+ width: "32px",
750
+ height: "32px",
751
+ } }, MIC_ICON_SVG),
570
752
  React.createElement("button", { type: "submit", disabled: isLoading || !inputValue.trim(), style: {
571
753
  position: 'absolute',
572
754
  right: theme.spacing.sm,
@@ -648,5 +830,5 @@ function createCustomTheme(baseTheme, overrides) {
648
830
  return Object.assign(Object.assign(Object.assign({}, baseTheme), overrides), { colors: Object.assign(Object.assign({}, baseTheme.colors), (overrides.colors || {})), spacing: Object.assign(Object.assign({}, baseTheme.spacing), (overrides.spacing || {})), fontSizes: Object.assign(Object.assign({}, baseTheme.fontSizes), (overrides.fontSizes || {})) });
649
831
  }
650
832
 
651
- export { ReactBridgeAPI, ReactBridgeChatbox, ReactBridgeProvider, ReactBridgeSearch, createCustomTheme, darkTheme, getTheme, lightTheme, useReactBridge, useReactBridgeContext };
833
+ export { ReactBridgeAPI, ReactBridgeChatbox, ReactBridgeProvider, ReactBridgeSearch, WebSpeechSTTProvider, WebSpeechTTSProvider, createCustomTheme, darkTheme, getTheme, lightTheme, useReactBridge, useReactBridgeContext };
652
834
  //# sourceMappingURL=index.esm.js.map