reactbridge-sdk 0.1.11 → 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 +76 -0
- package/dist/components/ReactBridgeChatbox.d.ts +5 -1
- package/dist/components/ReactBridgeChatbox.d.ts.map +1 -1
- package/dist/components/ReactBridgeSearch.d.ts +5 -1
- package/dist/components/ReactBridgeSearch.d.ts.map +1 -1
- package/dist/hooks/useReactBridge.d.ts +1 -1
- package/dist/hooks/useReactBridge.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +195 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +195 -11
- package/dist/index.js.map +1 -1
- package/dist/provider/ReactBridgeProvider.d.ts +8 -2
- package/dist/provider/ReactBridgeProvider.d.ts.map +1 -1
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/voice.d.ts +15 -0
- package/dist/utils/voice.d.ts.map +1 -0
- package/package.json +2 -1
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;
|
|
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;
|
|
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,
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
@@ -441,9 +584,9 @@ toggleButtonClass = defaultToggleButtonClass, toggleButtonTitle = "Open chat ass
|
|
|
441
584
|
: { bottom: "24px", left: "24px" };
|
|
442
585
|
if (!isOpen) {
|
|
443
586
|
// Render the toggle button when closed
|
|
444
|
-
return (React.createElement("button", { className: toggleButtonClass, onClick: () => setIsOpen(true), title: toggleButtonTitle, style: Object.assign(Object.assign({
|
|
587
|
+
return (React.createElement("button", { className: toggleButtonClass, onClick: () => setIsOpen(true), title: toggleButtonTitle, style: Object.assign(Object.assign(Object.assign({
|
|
445
588
|
// Apply widget-specific fixed positioning
|
|
446
|
-
position: "fixed", zIndex: 40 }, togglePositionClass), (toggleButtonClass === defaultToggleButtonClass && {
|
|
589
|
+
position: "fixed", zIndex: 40 }, togglePositionClass), { backgroundColor: finalHeaderBgColor }), (toggleButtonClass === defaultToggleButtonClass && {
|
|
447
590
|
width: '56px', height: '56px', borderRadius: '50%', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
|
448
591
|
})) }, toggleIcon));
|
|
449
592
|
}
|
|
@@ -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
|
-
|
|
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: '
|
|
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
|