@navsi.ai/sdk 1.0.0
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 +348 -0
- package/dist/chunk-427NHGTX.js +3 -0
- package/dist/chunk-427NHGTX.js.map +1 -0
- package/dist/chunk-6FUUG5WB.js +77 -0
- package/dist/chunk-6FUUG5WB.js.map +1 -0
- package/dist/chunk-EHZXIZIP.js +3752 -0
- package/dist/chunk-EHZXIZIP.js.map +1 -0
- package/dist/components/index.d.ts +66 -0
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +104 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +286 -0
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { NavigationAdapter, ServerAction, FlatWidgetConfig, WidgetConfig } from '@navsi.ai/shared';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ChatbotProvider Component
|
|
6
|
+
*
|
|
7
|
+
* Main provider component that initializes the chatbot SDK.
|
|
8
|
+
* Manages WebSocket connection, message state, and provides context to children.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface ChatbotProviderProps {
|
|
12
|
+
/** API key for authentication */
|
|
13
|
+
apiKey: string;
|
|
14
|
+
/** WebSocket server URL */
|
|
15
|
+
serverUrl: string;
|
|
16
|
+
/** Navigation adapter (defaults to memory adapter) */
|
|
17
|
+
navigationAdapter?: NavigationAdapter;
|
|
18
|
+
/** Server actions to register */
|
|
19
|
+
serverActions?: ServerAction[];
|
|
20
|
+
/** Widget config from API (flat shape). Fetched e.g. from GET /api/sdk/config. Takes precedence over options when merged. */
|
|
21
|
+
config?: FlatWidgetConfig | null;
|
|
22
|
+
/** Widget configuration (nested shape). Merged with config if both provided. */
|
|
23
|
+
options?: Partial<WidgetConfig>;
|
|
24
|
+
/** Optional list of available routes (included in page context for the AI) */
|
|
25
|
+
routes?: string[];
|
|
26
|
+
/** Enable debug mode */
|
|
27
|
+
debug?: boolean;
|
|
28
|
+
/** Auto-connect on mount */
|
|
29
|
+
autoConnect?: boolean;
|
|
30
|
+
/** Children components */
|
|
31
|
+
children: React__default.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
declare function ChatbotProvider({ apiKey, serverUrl, navigationAdapter, serverActions, config: configFromApi, options, routes, debug, autoConnect, children, }: ChatbotProviderProps): React__default.ReactElement;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ChatbotWidget Component
|
|
37
|
+
*
|
|
38
|
+
* Floating chat widget UI component.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
interface ChatbotWidgetProps {
|
|
42
|
+
/** Custom class name for the widget container */
|
|
43
|
+
className?: string;
|
|
44
|
+
/** Custom class name for the widget window */
|
|
45
|
+
windowClassName?: string;
|
|
46
|
+
/** Custom class name for the toggle button */
|
|
47
|
+
buttonClassName?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Floating chat widget component. Open state is stored in context so the panel
|
|
51
|
+
* stays open across navigation (e.g. when using Action mode).
|
|
52
|
+
* Supports Ask and Action modes for text chat.
|
|
53
|
+
*
|
|
54
|
+
* All styling is controlled via CSS classes. Users have full control over appearance.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <ChatbotProvider apiKey="..." serverUrl="...">
|
|
59
|
+
* <App />
|
|
60
|
+
* <ChatbotWidget className="my-chatbot" />
|
|
61
|
+
* </ChatbotProvider>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function ChatbotWidget({ className, windowClassName, buttonClassName }: ChatbotWidgetProps): React__default.ReactElement;
|
|
65
|
+
|
|
66
|
+
export { ChatbotProvider, type ChatbotProviderProps, ChatbotWidget, type ChatbotWidgetProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { WidgetConfig, Message, ChatMode, ServerAction, ConnectionState, ExecutionState } from '@navsi.ai/shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useChatbot Hook
|
|
5
|
+
*
|
|
6
|
+
* Main hook for accessing chatbot functionality.
|
|
7
|
+
* Provides simplified API for common operations.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface UseChatbotReturn {
|
|
11
|
+
/** Widget styling/copy from admin (or options). Use in ChatbotWidget. */
|
|
12
|
+
widgetConfig: Partial<WidgetConfig> | null;
|
|
13
|
+
/** Effective voice/response language (user-selected or config). Use for STT/TTS and locale. */
|
|
14
|
+
voiceLanguage: string | undefined;
|
|
15
|
+
setVoiceLanguage: (lang: string | undefined) => void;
|
|
16
|
+
messages: Message[];
|
|
17
|
+
sendMessage: (content: string) => void;
|
|
18
|
+
clearMessages: () => void;
|
|
19
|
+
mode: ChatMode;
|
|
20
|
+
setMode: (mode: ChatMode) => void;
|
|
21
|
+
isWidgetOpen: boolean;
|
|
22
|
+
setWidgetOpen: (open: boolean) => void;
|
|
23
|
+
isConnected: boolean;
|
|
24
|
+
connect: () => void;
|
|
25
|
+
disconnect: () => void;
|
|
26
|
+
executeAction: (actionId: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
27
|
+
registerServerAction: (action: ServerAction) => void;
|
|
28
|
+
stopExecution: () => void;
|
|
29
|
+
isExecuting: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
clearError: () => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Main hook for chatbot functionality
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* function MyComponent() {
|
|
39
|
+
* const {
|
|
40
|
+
* messages,
|
|
41
|
+
* sendMessage,
|
|
42
|
+
* isConnected,
|
|
43
|
+
* mode,
|
|
44
|
+
* setMode,
|
|
45
|
+
* } = useChatbot();
|
|
46
|
+
*
|
|
47
|
+
* return (
|
|
48
|
+
* <div>
|
|
49
|
+
* <button onClick={() => sendMessage('Hello!')}>
|
|
50
|
+
* Send
|
|
51
|
+
* </button>
|
|
52
|
+
* <button onClick={() => startVoice()}>
|
|
53
|
+
* Start Voice
|
|
54
|
+
* </button>
|
|
55
|
+
* </div>
|
|
56
|
+
* );
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function useChatbot(): UseChatbotReturn;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* useWebSocket Hook
|
|
64
|
+
*
|
|
65
|
+
* Hook for managing WebSocket connection state.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
interface UseWebSocketReturn {
|
|
69
|
+
connectionState: ConnectionState;
|
|
70
|
+
isConnected: boolean;
|
|
71
|
+
isConnecting: boolean;
|
|
72
|
+
isReconnecting: boolean;
|
|
73
|
+
connect: () => void;
|
|
74
|
+
disconnect: () => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Hook for WebSocket connection management
|
|
78
|
+
*/
|
|
79
|
+
declare function useWebSocket(): UseWebSocketReturn;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* useActionExecution Hook
|
|
83
|
+
*
|
|
84
|
+
* Hook for monitoring action execution state.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
interface UseActionExecutionReturn {
|
|
88
|
+
isExecuting: boolean;
|
|
89
|
+
executionState: ExecutionState | null;
|
|
90
|
+
currentCommand: ExecutionState['currentCommand'] | undefined;
|
|
91
|
+
progress: {
|
|
92
|
+
current: number;
|
|
93
|
+
total: number;
|
|
94
|
+
percentage: number;
|
|
95
|
+
/** Optional human-readable description of the current step */
|
|
96
|
+
description?: string;
|
|
97
|
+
} | null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Hook for monitoring action execution
|
|
101
|
+
*/
|
|
102
|
+
declare function useActionExecution(): UseActionExecutionReturn;
|
|
103
|
+
|
|
104
|
+
export { type UseActionExecutionReturn, type UseChatbotReturn, type UseWebSocketReturn, useActionExecution, useChatbot, useWebSocket };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
export { ChatbotProvider, ChatbotProviderProps, ChatbotWidget, ChatbotWidgetProps } from './components/index.js';
|
|
2
|
+
export { UseActionExecutionReturn, UseChatbotReturn, UseWebSocketReturn, useActionExecution, useChatbot, useWebSocket } from './hooks/index.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { WidgetConfig, Message, ChatMode, ConnectionState as ConnectionState$1, ExecutionState, ServerAction, ClientMessage, WebSocketEvents, NavigationAdapter, PageContext } from '@navsi.ai/shared';
|
|
5
|
+
export { ActionDefinition, ChatMode, Command, ExecutionState, Message, NavigationAdapter, PageContext, SDKConfig, ServerAction, WidgetConfig, WidgetTheme } from '@navsi.ai/shared';
|
|
6
|
+
|
|
7
|
+
interface ChatbotContextValue {
|
|
8
|
+
/** Widget styling and copy (from admin / API). Applied by ChatbotWidget. */
|
|
9
|
+
widgetConfig: Partial<WidgetConfig> | null;
|
|
10
|
+
messages: Message[];
|
|
11
|
+
mode: ChatMode;
|
|
12
|
+
connectionState: ConnectionState$1;
|
|
13
|
+
isExecuting: boolean;
|
|
14
|
+
executionState: ExecutionState | null;
|
|
15
|
+
error: Error | null;
|
|
16
|
+
isWidgetOpen: boolean;
|
|
17
|
+
setWidgetOpen: (open: boolean) => void;
|
|
18
|
+
/** User-selected voice/response language (e.g. 'en-US', 'hi-IN'). Overrides widgetConfig.voiceLanguage when set. */
|
|
19
|
+
voiceLanguage: string | undefined;
|
|
20
|
+
setVoiceLanguage: (lang: string | undefined) => void;
|
|
21
|
+
sendMessage: (content: string) => void;
|
|
22
|
+
setMode: (mode: ChatMode) => void;
|
|
23
|
+
executeAction: (actionId: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
24
|
+
registerServerAction: (action: ServerAction) => void;
|
|
25
|
+
clearMessages: () => void;
|
|
26
|
+
clearError: () => void;
|
|
27
|
+
stopExecution: () => void;
|
|
28
|
+
connect: () => void;
|
|
29
|
+
disconnect: () => void;
|
|
30
|
+
isConnected: boolean;
|
|
31
|
+
}
|
|
32
|
+
declare const ChatbotContext: React.Context<ChatbotContextValue | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Hook to access chatbot context
|
|
35
|
+
* Must be used within ChatbotProvider
|
|
36
|
+
*/
|
|
37
|
+
declare function useChatbotContext(): ChatbotContextValue;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* WebSocket Client for AI Chatbot SDK
|
|
41
|
+
*
|
|
42
|
+
* Features:
|
|
43
|
+
* - Auto-reconnection with exponential backoff
|
|
44
|
+
* - Message queue for offline scenarios
|
|
45
|
+
* - Heartbeat mechanism (ping/pong every 30s)
|
|
46
|
+
* - Token refresh before expiry
|
|
47
|
+
* - Connection state management
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'failed';
|
|
51
|
+
interface WebSocketClientConfig {
|
|
52
|
+
/** WebSocket server URL */
|
|
53
|
+
serverUrl: string;
|
|
54
|
+
/** API key for authentication */
|
|
55
|
+
apiKey: string;
|
|
56
|
+
/** Enable auto-reconnection */
|
|
57
|
+
autoReconnect?: boolean;
|
|
58
|
+
/** Maximum reconnection attempts */
|
|
59
|
+
maxReconnectAttempts?: number;
|
|
60
|
+
/** Initial reconnection delay in ms */
|
|
61
|
+
reconnectDelay?: number;
|
|
62
|
+
/** Heartbeat interval in ms */
|
|
63
|
+
heartbeatInterval?: number;
|
|
64
|
+
/** Enable debug logging */
|
|
65
|
+
debug?: boolean;
|
|
66
|
+
}
|
|
67
|
+
type EventCallback<T> = (data: T) => void;
|
|
68
|
+
declare class WebSocketClient {
|
|
69
|
+
private ws;
|
|
70
|
+
private config;
|
|
71
|
+
private state;
|
|
72
|
+
private logger;
|
|
73
|
+
private reconnectAttempts;
|
|
74
|
+
private reconnectTimeout;
|
|
75
|
+
private heartbeatInterval;
|
|
76
|
+
private lastPongTime;
|
|
77
|
+
private messageQueue;
|
|
78
|
+
private sessionId;
|
|
79
|
+
private refreshToken;
|
|
80
|
+
private tokenExpiresAt;
|
|
81
|
+
private tokenRefreshTimeout;
|
|
82
|
+
private listeners;
|
|
83
|
+
constructor(config: WebSocketClientConfig);
|
|
84
|
+
/**
|
|
85
|
+
* Connect to the WebSocket server
|
|
86
|
+
*/
|
|
87
|
+
connect(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Disconnect from the server
|
|
90
|
+
*/
|
|
91
|
+
disconnect(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Send a message to the server
|
|
94
|
+
*/
|
|
95
|
+
send(message: ClientMessage): void;
|
|
96
|
+
/**
|
|
97
|
+
* Get current connection state
|
|
98
|
+
*/
|
|
99
|
+
getState(): ConnectionState;
|
|
100
|
+
/**
|
|
101
|
+
* Get session ID
|
|
102
|
+
*/
|
|
103
|
+
getSessionId(): string | null;
|
|
104
|
+
/**
|
|
105
|
+
* Check if connected
|
|
106
|
+
*/
|
|
107
|
+
isConnected(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to an event
|
|
110
|
+
*/
|
|
111
|
+
on<K extends keyof WebSocketEvents>(event: K, callback: EventCallback<WebSocketEvents[K]>): () => void;
|
|
112
|
+
/**
|
|
113
|
+
* Emit an event to all listeners
|
|
114
|
+
*/
|
|
115
|
+
private emit;
|
|
116
|
+
private createWebSocket;
|
|
117
|
+
private handleOpen;
|
|
118
|
+
private handleMessage;
|
|
119
|
+
private handleClose;
|
|
120
|
+
private handleError;
|
|
121
|
+
private handleConnected;
|
|
122
|
+
private handleAuthSuccess;
|
|
123
|
+
private handleAuthError;
|
|
124
|
+
private scheduleTokenRefresh;
|
|
125
|
+
private refreshAccessToken;
|
|
126
|
+
private handleTokenRefreshed;
|
|
127
|
+
private startHeartbeat;
|
|
128
|
+
private stopHeartbeat;
|
|
129
|
+
private handlePong;
|
|
130
|
+
private scheduleReconnect;
|
|
131
|
+
private handleConnectionFailure;
|
|
132
|
+
private flushMessageQueue;
|
|
133
|
+
private setState;
|
|
134
|
+
private cleanup;
|
|
135
|
+
private log;
|
|
136
|
+
}
|
|
137
|
+
declare function createWebSocketClient(config: WebSocketClientConfig): WebSocketClient;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Navigation Adapter Interface
|
|
141
|
+
*
|
|
142
|
+
* Abstract interface for routing library integration.
|
|
143
|
+
* Companies can implement this for any routing solution:
|
|
144
|
+
* - React Router v6
|
|
145
|
+
* - Next.js Router
|
|
146
|
+
* - Reach Router
|
|
147
|
+
* - Custom routing
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a simple in-memory navigation adapter
|
|
152
|
+
* Useful for testing or non-SPA applications
|
|
153
|
+
*/
|
|
154
|
+
declare function createMemoryAdapter(initialPath?: string): NavigationAdapter;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* React Router v6 Adapter
|
|
158
|
+
*
|
|
159
|
+
* Provides navigation adapter implementation for React Router v6.
|
|
160
|
+
* This is a hook that creates an adapter from React Router hooks.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Type for React Router's navigate function
|
|
165
|
+
*/
|
|
166
|
+
type NavigateFunction = (to: string) => void;
|
|
167
|
+
/**
|
|
168
|
+
* Type for React Router's location object
|
|
169
|
+
*/
|
|
170
|
+
interface RouterLocation {
|
|
171
|
+
pathname: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Creates a React Router v6 navigation adapter
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* import { useNavigate, useLocation } from 'react-router-dom';
|
|
179
|
+
*
|
|
180
|
+
* function App() {
|
|
181
|
+
* const navigate = useNavigate();
|
|
182
|
+
* const location = useLocation();
|
|
183
|
+
* const adapter = useReactRouterAdapter(navigate, location);
|
|
184
|
+
*
|
|
185
|
+
* return (
|
|
186
|
+
* <ChatbotProvider navigationAdapter={adapter}>
|
|
187
|
+
* ...
|
|
188
|
+
* </ChatbotProvider>
|
|
189
|
+
* );
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
declare function useReactRouterAdapter(navigate: NavigateFunction, location: RouterLocation): NavigationAdapter;
|
|
194
|
+
/**
|
|
195
|
+
* Create a React Router adapter from outside React context
|
|
196
|
+
* Useful when you need to create the adapter before rendering
|
|
197
|
+
*
|
|
198
|
+
* @param getNavigate Function that returns the navigate function
|
|
199
|
+
* @param getLocation Function that returns the current location
|
|
200
|
+
*/
|
|
201
|
+
declare function createReactRouterAdapter(getNavigate: () => NavigateFunction, getLocation: () => RouterLocation, subscribeToLocation: (callback: (location: RouterLocation) => void) => () => void): NavigationAdapter;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Navigation Controller
|
|
205
|
+
*
|
|
206
|
+
* Coordinates navigation and DOM stability detection for cross-route actions.
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
interface NavigationControllerConfig {
|
|
210
|
+
/** DOM stability quiet period in ms */
|
|
211
|
+
domStabilityDelay?: number;
|
|
212
|
+
/** Maximum wait time for DOM stability */
|
|
213
|
+
domStabilityTimeout?: number;
|
|
214
|
+
/** Enable debug logging */
|
|
215
|
+
debug?: boolean;
|
|
216
|
+
/** Called when DOM has become stable (e.g. to re-scan and update action registry) */
|
|
217
|
+
onDOMStable?: () => void;
|
|
218
|
+
}
|
|
219
|
+
declare class NavigationController {
|
|
220
|
+
private adapter;
|
|
221
|
+
private config;
|
|
222
|
+
private scanCallback;
|
|
223
|
+
private logger;
|
|
224
|
+
constructor(adapter: NavigationAdapter, config?: NavigationControllerConfig);
|
|
225
|
+
/**
|
|
226
|
+
* Set the page scanner callback
|
|
227
|
+
*/
|
|
228
|
+
setScanner(scanner: () => Promise<PageContext>): void;
|
|
229
|
+
/**
|
|
230
|
+
* Get current route
|
|
231
|
+
*/
|
|
232
|
+
getCurrentRoute(): string;
|
|
233
|
+
/**
|
|
234
|
+
* Navigate to a route and wait for it to load
|
|
235
|
+
*/
|
|
236
|
+
navigateTo(route: string, timeout?: number): Promise<boolean>;
|
|
237
|
+
/**
|
|
238
|
+
* Execute navigation and rescan page context
|
|
239
|
+
*/
|
|
240
|
+
executeNavigation(route: string): Promise<PageContext | null>;
|
|
241
|
+
/**
|
|
242
|
+
* Wait for DOM to become stable after navigation
|
|
243
|
+
* Uses MutationObserver to detect when DOM stops changing
|
|
244
|
+
*/
|
|
245
|
+
waitForDOMStable(): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* Subscribe to route changes
|
|
248
|
+
*/
|
|
249
|
+
onRouteChange(callback: (route: string) => void): () => void;
|
|
250
|
+
private log;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Create a navigation controller
|
|
254
|
+
*/
|
|
255
|
+
declare function createNavigationController(adapter: NavigationAdapter, config?: NavigationControllerConfig): NavigationController;
|
|
256
|
+
|
|
257
|
+
/** Next.js App Router: router from useRouter(), pathname from usePathname() */
|
|
258
|
+
type NextRouterLike = {
|
|
259
|
+
push: (href: string) => void;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Creates a Next.js App Router navigation adapter.
|
|
263
|
+
* Must be used in a Client Component. Requires next to be installed.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```tsx
|
|
267
|
+
* 'use client';
|
|
268
|
+
* import { useRouter, usePathname } from 'next/navigation';
|
|
269
|
+
* import { ChatbotProvider, ChatbotWidget, useNextRouterAdapter } from '@navsi/sdk';
|
|
270
|
+
*
|
|
271
|
+
* function ChatbotWrapper({ children }: { children: React.ReactNode }) {
|
|
272
|
+
* const router = useRouter();
|
|
273
|
+
* const pathname = usePathname();
|
|
274
|
+
* const adapter = useNextRouterAdapter(router, pathname);
|
|
275
|
+
* return (
|
|
276
|
+
* <ChatbotProvider apiKey="..." serverUrl="..." navigationAdapter={adapter}>
|
|
277
|
+
* {children}
|
|
278
|
+
* <ChatbotWidget />
|
|
279
|
+
* </ChatbotProvider>
|
|
280
|
+
* );
|
|
281
|
+
* }
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
declare function useNextRouterAdapter(router: NextRouterLike, pathname: string): NavigationAdapter;
|
|
285
|
+
|
|
286
|
+
export { ChatbotContext, type ChatbotContextValue, type ConnectionState, type NavigateFunction, NavigationController, type NavigationControllerConfig, type NextRouterLike, type RouterLocation, WebSocketClient, type WebSocketClientConfig, createMemoryAdapter, createNavigationController, createReactRouterAdapter, createWebSocketClient, useChatbotContext, useNextRouterAdapter, useReactRouterAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { DEFAULT_ROUTE_TIMEOUT } from './chunk-EHZXIZIP.js';
|
|
2
|
+
export { ChatbotProvider, ChatbotWidget, NavigationController, WebSocketClient, createMemoryAdapter, createNavigationController, createWebSocketClient } from './chunk-EHZXIZIP.js';
|
|
3
|
+
import './chunk-427NHGTX.js';
|
|
4
|
+
export { ChatbotContext, useActionExecution, useChatbot, useChatbotContext, useWebSocket } from './chunk-6FUUG5WB.js';
|
|
5
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
function useReactRouterAdapter(navigate, location) {
|
|
8
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
9
|
+
const currentPathRef = useRef(location.pathname);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (location.pathname !== currentPathRef.current) {
|
|
12
|
+
currentPathRef.current = location.pathname;
|
|
13
|
+
listenersRef.current.forEach((callback) => {
|
|
14
|
+
callback(location.pathname);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}, [location.pathname]);
|
|
18
|
+
const navigateTo = useCallback((path) => {
|
|
19
|
+
navigate(path);
|
|
20
|
+
}, [navigate]);
|
|
21
|
+
const getCurrentPath = useCallback(() => {
|
|
22
|
+
return currentPathRef.current;
|
|
23
|
+
}, []);
|
|
24
|
+
const onRouteChange = useCallback((callback) => {
|
|
25
|
+
listenersRef.current.add(callback);
|
|
26
|
+
return () => {
|
|
27
|
+
listenersRef.current.delete(callback);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
const waitForRoute = useCallback(async (path, timeout = DEFAULT_ROUTE_TIMEOUT) => {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
if (currentPathRef.current === path) {
|
|
33
|
+
resolve(true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let resolved = false;
|
|
37
|
+
const unsubscribe = onRouteChange((newPath) => {
|
|
38
|
+
if (newPath === path && !resolved) {
|
|
39
|
+
resolved = true;
|
|
40
|
+
unsubscribe();
|
|
41
|
+
resolve(true);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
resolved = true;
|
|
47
|
+
unsubscribe();
|
|
48
|
+
resolve(currentPathRef.current === path);
|
|
49
|
+
}
|
|
50
|
+
}, timeout);
|
|
51
|
+
});
|
|
52
|
+
}, [onRouteChange]);
|
|
53
|
+
const adapterRef = useRef(null);
|
|
54
|
+
if (!adapterRef.current) {
|
|
55
|
+
adapterRef.current = {
|
|
56
|
+
navigate: navigateTo,
|
|
57
|
+
getCurrentPath,
|
|
58
|
+
onRouteChange,
|
|
59
|
+
waitForRoute
|
|
60
|
+
};
|
|
61
|
+
} else {
|
|
62
|
+
adapterRef.current.navigate = navigateTo;
|
|
63
|
+
adapterRef.current.getCurrentPath = getCurrentPath;
|
|
64
|
+
adapterRef.current.onRouteChange = onRouteChange;
|
|
65
|
+
adapterRef.current.waitForRoute = waitForRoute;
|
|
66
|
+
}
|
|
67
|
+
return adapterRef.current;
|
|
68
|
+
}
|
|
69
|
+
function createReactRouterAdapter(getNavigate, getLocation, subscribeToLocation) {
|
|
70
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
71
|
+
subscribeToLocation((location) => {
|
|
72
|
+
listeners.forEach((callback) => callback(location.pathname));
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
navigate(path) {
|
|
76
|
+
getNavigate()(path);
|
|
77
|
+
},
|
|
78
|
+
getCurrentPath() {
|
|
79
|
+
return getLocation().pathname;
|
|
80
|
+
},
|
|
81
|
+
onRouteChange(callback) {
|
|
82
|
+
listeners.add(callback);
|
|
83
|
+
return () => listeners.delete(callback);
|
|
84
|
+
},
|
|
85
|
+
async waitForRoute(path, timeout = DEFAULT_ROUTE_TIMEOUT) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const currentPath = getLocation().pathname;
|
|
88
|
+
if (currentPath === path) {
|
|
89
|
+
resolve(true);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let resolved = false;
|
|
93
|
+
const unsubscribe = this.onRouteChange((newPath) => {
|
|
94
|
+
if (newPath === path && !resolved) {
|
|
95
|
+
resolved = true;
|
|
96
|
+
unsubscribe();
|
|
97
|
+
resolve(true);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
if (!resolved) {
|
|
102
|
+
resolved = true;
|
|
103
|
+
unsubscribe();
|
|
104
|
+
resolve(getLocation().pathname === path);
|
|
105
|
+
}
|
|
106
|
+
}, timeout);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function useNextRouterAdapter(router, pathname) {
|
|
112
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
113
|
+
const currentPathRef = useRef(pathname);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (pathname !== currentPathRef.current) {
|
|
116
|
+
currentPathRef.current = pathname;
|
|
117
|
+
listenersRef.current.forEach((callback) => {
|
|
118
|
+
callback(pathname);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}, [pathname]);
|
|
122
|
+
const navigateTo = useCallback(
|
|
123
|
+
(path) => {
|
|
124
|
+
router.push(path);
|
|
125
|
+
},
|
|
126
|
+
[router]
|
|
127
|
+
);
|
|
128
|
+
const getCurrentPath = useCallback(() => {
|
|
129
|
+
return currentPathRef.current;
|
|
130
|
+
}, []);
|
|
131
|
+
const onRouteChange = useCallback((callback) => {
|
|
132
|
+
listenersRef.current.add(callback);
|
|
133
|
+
return () => {
|
|
134
|
+
listenersRef.current.delete(callback);
|
|
135
|
+
};
|
|
136
|
+
}, []);
|
|
137
|
+
const waitForRoute = useCallback(
|
|
138
|
+
async (path, timeout = DEFAULT_ROUTE_TIMEOUT) => {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
if (currentPathRef.current === path) {
|
|
141
|
+
resolve(true);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let resolved = false;
|
|
145
|
+
const unsubscribe = onRouteChange((newPath) => {
|
|
146
|
+
if (newPath === path && !resolved) {
|
|
147
|
+
resolved = true;
|
|
148
|
+
unsubscribe();
|
|
149
|
+
resolve(true);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
if (!resolved) {
|
|
154
|
+
resolved = true;
|
|
155
|
+
unsubscribe();
|
|
156
|
+
resolve(currentPathRef.current === path);
|
|
157
|
+
}
|
|
158
|
+
}, timeout);
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
[onRouteChange]
|
|
162
|
+
);
|
|
163
|
+
const adapterRef = useRef(null);
|
|
164
|
+
if (!adapterRef.current) {
|
|
165
|
+
adapterRef.current = {
|
|
166
|
+
navigate: navigateTo,
|
|
167
|
+
getCurrentPath,
|
|
168
|
+
onRouteChange,
|
|
169
|
+
waitForRoute
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
adapterRef.current.navigate = navigateTo;
|
|
173
|
+
adapterRef.current.getCurrentPath = getCurrentPath;
|
|
174
|
+
adapterRef.current.onRouteChange = onRouteChange;
|
|
175
|
+
adapterRef.current.waitForRoute = waitForRoute;
|
|
176
|
+
}
|
|
177
|
+
return adapterRef.current;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { createReactRouterAdapter, useNextRouterAdapter, useReactRouterAdapter };
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/navigation/react-router-adapter.ts","../src/core/navigation/nextjs-adapter.ts"],"names":["useRef","useEffect","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * React Router v6 Adapter\n * \n * Provides navigation adapter implementation for React Router v6.\n * This is a hook that creates an adapter from React Router hooks.\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { NavigationAdapter } from '@navsi.ai/shared';\nimport { DEFAULT_ROUTE_TIMEOUT } from './adapter.js';\n\n/**\n * Type for React Router's navigate function\n */\nexport type NavigateFunction = (to: string) => void;\n\n/**\n * Type for React Router's location object\n */\nexport interface RouterLocation {\n pathname: string;\n}\n\n/**\n * Creates a React Router v6 navigation adapter\n * \n * @example\n * ```tsx\n * import { useNavigate, useLocation } from 'react-router-dom';\n * \n * function App() {\n * const navigate = useNavigate();\n * const location = useLocation();\n * const adapter = useReactRouterAdapter(navigate, location);\n * \n * return (\n * <ChatbotProvider navigationAdapter={adapter}>\n * ...\n * </ChatbotProvider>\n * );\n * }\n * ```\n */\nexport function useReactRouterAdapter(\n navigate: NavigateFunction,\n location: RouterLocation\n): NavigationAdapter {\n const listenersRef = useRef<Set<(path: string) => void>>(new Set());\n const currentPathRef = useRef(location.pathname);\n\n // Update current path and notify listeners when location changes\n useEffect(() => {\n if (location.pathname !== currentPathRef.current) {\n currentPathRef.current = location.pathname;\n listenersRef.current.forEach((callback) => {\n callback(location.pathname);\n });\n }\n }, [location.pathname]);\n\n const navigateTo = useCallback((path: string) => {\n navigate(path);\n }, [navigate]);\n\n const getCurrentPath = useCallback(() => {\n return currentPathRef.current;\n }, []);\n\n const onRouteChange = useCallback((callback: (path: string) => void) => {\n listenersRef.current.add(callback);\n return () => {\n listenersRef.current.delete(callback);\n };\n }, []);\n\n const waitForRoute = useCallback(async (path: string, timeout = DEFAULT_ROUTE_TIMEOUT): Promise<boolean> => {\n return new Promise((resolve) => {\n if (currentPathRef.current === path) {\n resolve(true);\n return;\n }\n\n let resolved = false;\n\n const unsubscribe = onRouteChange((newPath) => {\n if (newPath === path && !resolved) {\n resolved = true;\n unsubscribe();\n resolve(true);\n }\n });\n\n setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n resolve(currentPathRef.current === path);\n }\n }, timeout);\n });\n }, [onRouteChange]);\n\n // Return a stable reference to the adapter\n const adapterRef = useRef<NavigationAdapter | null>(null);\n\n if (!adapterRef.current) {\n adapterRef.current = {\n navigate: navigateTo,\n getCurrentPath,\n onRouteChange,\n waitForRoute,\n };\n } else {\n // Update functions to use latest refs\n adapterRef.current.navigate = navigateTo;\n adapterRef.current.getCurrentPath = getCurrentPath;\n adapterRef.current.onRouteChange = onRouteChange;\n adapterRef.current.waitForRoute = waitForRoute;\n }\n\n return adapterRef.current;\n}\n\n/**\n * Create a React Router adapter from outside React context\n * Useful when you need to create the adapter before rendering\n * \n * @param getNavigate Function that returns the navigate function\n * @param getLocation Function that returns the current location\n */\nexport function createReactRouterAdapter(\n getNavigate: () => NavigateFunction,\n getLocation: () => RouterLocation,\n subscribeToLocation: (callback: (location: RouterLocation) => void) => () => void\n): NavigationAdapter {\n const listeners = new Set<(path: string) => void>();\n\n // Subscribe to location changes\n subscribeToLocation((location) => {\n listeners.forEach((callback) => callback(location.pathname));\n });\n\n return {\n navigate(path: string): void {\n getNavigate()(path);\n },\n\n getCurrentPath(): string {\n return getLocation().pathname;\n },\n\n onRouteChange(callback: (path: string) => void): () => void {\n listeners.add(callback);\n return () => listeners.delete(callback);\n },\n\n async waitForRoute(path: string, timeout = DEFAULT_ROUTE_TIMEOUT): Promise<boolean> {\n return new Promise((resolve) => {\n const currentPath = getLocation().pathname;\n\n if (currentPath === path) {\n resolve(true);\n return;\n }\n\n let resolved = false;\n\n const unsubscribe = this.onRouteChange((newPath) => {\n if (newPath === path && !resolved) {\n resolved = true;\n unsubscribe();\n resolve(true);\n }\n });\n\n setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n resolve(getLocation().pathname === path);\n }\n }, timeout);\n });\n },\n };\n}\n","'use client';\n/**\n * Next.js App Router Adapter\n *\n * Provides navigation adapter implementation for Next.js App Router.\n * Uses next/navigation: useRouter(), usePathname().\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { NavigationAdapter } from '@navsi.ai/shared';\nimport { DEFAULT_ROUTE_TIMEOUT } from './adapter.js';\n\n/** Next.js App Router: router from useRouter(), pathname from usePathname() */\nexport type NextRouterLike = { push: (href: string) => void };\n\n/**\n * Creates a Next.js App Router navigation adapter.\n * Must be used in a Client Component. Requires next to be installed.\n *\n * @example\n * ```tsx\n * 'use client';\n * import { useRouter, usePathname } from 'next/navigation';\n * import { ChatbotProvider, ChatbotWidget, useNextRouterAdapter } from '@navsi/sdk';\n *\n * function ChatbotWrapper({ children }: { children: React.ReactNode }) {\n * const router = useRouter();\n * const pathname = usePathname();\n * const adapter = useNextRouterAdapter(router, pathname);\n * return (\n * <ChatbotProvider apiKey=\"...\" serverUrl=\"...\" navigationAdapter={adapter}>\n * {children}\n * <ChatbotWidget />\n * </ChatbotProvider>\n * );\n * }\n * ```\n */\nexport function useNextRouterAdapter(\n router: NextRouterLike,\n pathname: string\n): NavigationAdapter {\n const listenersRef = useRef<Set<(path: string) => void>>(new Set());\n const currentPathRef = useRef(pathname);\n\n useEffect(() => {\n if (pathname !== currentPathRef.current) {\n currentPathRef.current = pathname;\n listenersRef.current.forEach((callback) => {\n callback(pathname);\n });\n }\n }, [pathname]);\n\n const navigateTo = useCallback(\n (path: string) => {\n router.push(path);\n },\n [router]\n );\n\n const getCurrentPath = useCallback(() => {\n return currentPathRef.current;\n }, []);\n\n const onRouteChange = useCallback((callback: (path: string) => void) => {\n listenersRef.current.add(callback);\n return () => {\n listenersRef.current.delete(callback);\n };\n }, []);\n\n const waitForRoute = useCallback(\n async (path: string, timeout = DEFAULT_ROUTE_TIMEOUT): Promise<boolean> => {\n return new Promise((resolve) => {\n if (currentPathRef.current === path) {\n resolve(true);\n return;\n }\n\n let resolved = false;\n\n const unsubscribe = onRouteChange((newPath) => {\n if (newPath === path && !resolved) {\n resolved = true;\n unsubscribe();\n resolve(true);\n }\n });\n\n setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n resolve(currentPathRef.current === path);\n }\n }, timeout);\n });\n },\n [onRouteChange]\n );\n\n const adapterRef = useRef<NavigationAdapter | null>(null);\n\n if (!adapterRef.current) {\n adapterRef.current = {\n navigate: navigateTo,\n getCurrentPath,\n onRouteChange,\n waitForRoute,\n };\n } else {\n adapterRef.current.navigate = navigateTo;\n adapterRef.current.getCurrentPath = getCurrentPath;\n adapterRef.current.onRouteChange = onRouteChange;\n adapterRef.current.waitForRoute = waitForRoute;\n }\n\n return adapterRef.current;\n}\n"]}
|