fyrebot-widget 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/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/ChatbotWidget.d.ts +9 -0
- package/dist/ChatbotWidget.d.ts.map +1 -0
- package/dist/ChatbotWidget.js +479 -0
- package/dist/api.d.ts +33 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +98 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fyrebot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# 🤖 Fyrebot AI Chatbot Widget
|
|
2
|
+
|
|
3
|
+
Production-ready, standalone chatbot popup widget for any React application. Features beautiful dark theme, multi-tenant support via X-API-Key authentication, and complete TypeScript support.
|
|
4
|
+
|
|
5
|
+
**Made with ❤️ by [Fyrebot](https://fyrebot.com)**
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- 🎨 Beautiful dark theme UI with smooth animations
|
|
10
|
+
- 🔐 Secure X-API-Key authentication
|
|
11
|
+
- 🏢 Multi-tenant support (automatic tenant identification via API key)
|
|
12
|
+
- 💬 Session-based conversations with context retention
|
|
13
|
+
- 📚 Source citations with relevance scores
|
|
14
|
+
- ⚡ Real-time typing indicators
|
|
15
|
+
- 📱 Fully responsive design
|
|
16
|
+
- 🎯 TypeScript support with complete type definitions
|
|
17
|
+
- 🔄 Error handling with user-friendly messages
|
|
18
|
+
- ⚙️ Highly customizable (colors, position, messages, etc.)
|
|
19
|
+
- 📦 Zero external UI dependencies (self-contained)
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install fyrebot-widget
|
|
25
|
+
# or
|
|
26
|
+
yarn add fyrebot-widget
|
|
27
|
+
# or
|
|
28
|
+
pnpm add fyrebot-widget
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 🚀 Quick Start
|
|
32
|
+
|
|
33
|
+
### Basic Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { ChatbotWidget } from 'fyrebot-widget';
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
{/* Your app content */}
|
|
42
|
+
|
|
43
|
+
<ChatbotWidget
|
|
44
|
+
apiUrl="https://api.fyrebot.com/api"
|
|
45
|
+
apiKey="your-api-key-from-dashboard"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Get Your API Key
|
|
53
|
+
|
|
54
|
+
1. Sign up at [fyrebot.com](https://fyrebot.com)
|
|
55
|
+
2. Go to your dashboard
|
|
56
|
+
3. Copy your API key
|
|
57
|
+
4. Use it in the widget configuration
|
|
58
|
+
|
|
59
|
+
### Advanced Configuration
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { ChatbotWidget } from 'fyrebot-widget';
|
|
63
|
+
|
|
64
|
+
function App() {
|
|
65
|
+
return (
|
|
66
|
+
<ChatbotWidget
|
|
67
|
+
// Required
|
|
68
|
+
apiUrl="https://api.clarvobot.com/api"
|
|
69
|
+
apiKey="your-api-key-from-dashboard"
|
|
70
|
+
|
|
71
|
+
// Customization
|
|
72
|
+
title="Support Assistant"
|
|
73
|
+
subtitle="We're here to help"
|
|
74
|
+
brandName="Your Company"
|
|
75
|
+
primaryColor="#6366f1"
|
|
76
|
+
welcomeMessage="Hello! How can I assist you today?"
|
|
77
|
+
placeholder="Ask me anything..."
|
|
78
|
+
position="bottom-right"
|
|
79
|
+
|
|
80
|
+
// Features
|
|
81
|
+
showTypingIndicator={true}
|
|
82
|
+
showTimestamps={true}
|
|
83
|
+
showSources={true}
|
|
84
|
+
|
|
85
|
+
// Sizing
|
|
86
|
+
maxHeight="600px"
|
|
87
|
+
maxWidth="400px"
|
|
88
|
+
|
|
89
|
+
// Callbacks
|
|
90
|
+
onOpen={() => console.log('Chat opened')}
|
|
91
|
+
onClose={() => console.log('Chat closed')}
|
|
92
|
+
onMessageSent={(message) => console.log('Sent:', message)}
|
|
93
|
+
onMessageReceived={(message) => console.log('Received:', message)}
|
|
94
|
+
onError={(error) => console.error('Error:', error)}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 🎨 Customization Options
|
|
101
|
+
|
|
102
|
+
### Configuration Props
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Default | Description |
|
|
105
|
+
|------|------|---------|-------------|
|
|
106
|
+
| `apiUrl` | `string` | **Required** | Your API endpoint URL |
|
|
107
|
+
| `apiKey` | `string` | **Required** | X-API-Key for authentication |
|
|
108
|
+
| `title` | `string` | `'AI Assistant'` | Header title |
|
|
109
|
+
| `subtitle` | `string` | `'Ask me anything'` | Header subtitle |
|
|
110
|
+
| `brandName` | `string` | `undefined` | Brand name for welcome message |
|
|
111
|
+
| `primaryColor` | `string` | `'#6366f1'` | Primary theme color (hex) |
|
|
112
|
+
| `welcomeMessage` | `string` | `'Hello! How can I help you today?'` | Initial welcome message |
|
|
113
|
+
| `placeholder` | `string` | `'Type your message...'` | Input placeholder text |
|
|
114
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Chat button position |
|
|
115
|
+
| `showTypingIndicator` | `boolean` | `true` | Show typing animation |
|
|
116
|
+
| `showTimestamps` | `boolean` | `true` | Show message timestamps |
|
|
117
|
+
| `showSources` | `boolean` | `true` | Show source citations |
|
|
118
|
+
| `maxHeight` | `string` | `'600px'` | Maximum chat window height |
|
|
119
|
+
| `maxWidth` | `string` | `'400px'` | Maximum chat window width |
|
|
120
|
+
| `className` | `string` | `''` | Custom CSS class |
|
|
121
|
+
|
|
122
|
+
### Callbacks
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<ChatbotWidget
|
|
126
|
+
// ... other props
|
|
127
|
+
onOpen={() => {
|
|
128
|
+
// Called when chat window opens
|
|
129
|
+
console.log('Chat opened');
|
|
130
|
+
}}
|
|
131
|
+
|
|
132
|
+
onClose={() => {
|
|
133
|
+
// Called when chat window closes
|
|
134
|
+
console.log('Chat closed');
|
|
135
|
+
}}
|
|
136
|
+
|
|
137
|
+
onMessageSent={(message) => {
|
|
138
|
+
// Called when user sends a message
|
|
139
|
+
console.log('User said:', message);
|
|
140
|
+
}}
|
|
141
|
+
|
|
142
|
+
onMessageReceived={(message) => {
|
|
143
|
+
// Called when bot responds
|
|
144
|
+
console.log('Bot said:', message.content);
|
|
145
|
+
}}
|
|
146
|
+
|
|
147
|
+
onError={(error) => {
|
|
148
|
+
// Called when an error occurs
|
|
149
|
+
console.error('Error:', error.message);
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 🔐 API Integration
|
|
155
|
+
|
|
156
|
+
### Backend Requirements
|
|
157
|
+
|
|
158
|
+
Your backend API must support:
|
|
159
|
+
|
|
160
|
+
1. **Authentication**: X-API-Key header
|
|
161
|
+
2. **Endpoint**: `POST /chat`
|
|
162
|
+
3. **Request Format**:
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"query": "User's message",
|
|
166
|
+
"sessionId": "optional-session-id"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
4. **Response Format**:
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"success": true,
|
|
173
|
+
"data": {
|
|
174
|
+
"answer": "Bot's response",
|
|
175
|
+
"sessionId": "session-123",
|
|
176
|
+
"sources": [
|
|
177
|
+
{
|
|
178
|
+
"title": "Source document",
|
|
179
|
+
"score": 0.95,
|
|
180
|
+
"content": "Optional excerpt"
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Multi-Tenant Support
|
|
188
|
+
|
|
189
|
+
The widget automatically includes the X-API-Key header in all requests. Your backend should:
|
|
190
|
+
|
|
191
|
+
1. Validate the X-API-Key
|
|
192
|
+
2. Identify the tenant from the key
|
|
193
|
+
3. Return responses based on the tenant's data
|
|
194
|
+
|
|
195
|
+
Example backend logic:
|
|
196
|
+
```javascript
|
|
197
|
+
// Backend (Node.js/Express example)
|
|
198
|
+
app.post('/chat', async (req, res) => {
|
|
199
|
+
const apiKey = req.headers['x-api-key'];
|
|
200
|
+
|
|
201
|
+
// Validate and get tenant
|
|
202
|
+
const tenant = await validateApiKey(apiKey);
|
|
203
|
+
if (!tenant) {
|
|
204
|
+
return res.status(401).json({ error: 'Invalid API key' });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Process query with tenant's data
|
|
208
|
+
const result = await processQuery(
|
|
209
|
+
tenant.id,
|
|
210
|
+
req.body.query,
|
|
211
|
+
req.body.sessionId
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
res.json({ success: true, data: result });
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 🎯 TypeScript Support
|
|
219
|
+
|
|
220
|
+
Full TypeScript definitions included:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { ChatbotWidget, ChatbotConfig, ChatMessage } from 'fyrebot-widget';
|
|
224
|
+
|
|
225
|
+
const config: ChatbotConfig = {
|
|
226
|
+
apiUrl: 'https://api.fyrebot.com/api',
|
|
227
|
+
apiKey: 'your-key',
|
|
228
|
+
title: 'Assistant',
|
|
229
|
+
onMessageReceived: (message: ChatMessage) => {
|
|
230
|
+
console.log(message.content);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function App() {
|
|
235
|
+
return <ChatbotWidget {...config} />;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 📱 Responsive Design
|
|
240
|
+
|
|
241
|
+
The widget automatically adapts to:
|
|
242
|
+
- Desktop screens
|
|
243
|
+
- Tablets
|
|
244
|
+
- Mobile devices (full-screen on small screens)
|
|
245
|
+
|
|
246
|
+
## 🎨 Styling
|
|
247
|
+
|
|
248
|
+
The widget uses scoped inline styles with no external CSS dependencies. You can customize:
|
|
249
|
+
|
|
250
|
+
1. **Theme Colors**: Use `primaryColor` prop
|
|
251
|
+
2. **Size**: Use `maxHeight` and `maxWidth` props
|
|
252
|
+
3. **Position**: Use `position` prop
|
|
253
|
+
4. **Custom Overrides**: Use `className` prop with CSS variables
|
|
254
|
+
|
|
255
|
+
Example custom styling:
|
|
256
|
+
```css
|
|
257
|
+
.my-custom-chatbot {
|
|
258
|
+
/* Your custom styles */
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
<ChatbotWidget
|
|
264
|
+
className="my-custom-chatbot"
|
|
265
|
+
primaryColor="#ff6b6b"
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## 🔧 Advanced Usage
|
|
270
|
+
|
|
271
|
+
### Programmatic Control
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { ChatbotWidget, ChatbotApiClient } from 'fyrebot-widget';
|
|
275
|
+
import { useRef } from 'react';
|
|
276
|
+
|
|
277
|
+
function App() {
|
|
278
|
+
const apiClientRef = useRef<ChatbotApiClient>();
|
|
279
|
+
|
|
280
|
+
const handleReset = () => {
|
|
281
|
+
// Reset the conversation
|
|
282
|
+
apiClientRef.current?.resetSession();
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<>
|
|
287
|
+
<button onClick={handleReset}>Reset Chat</button>
|
|
288
|
+
<ChatbotWidget
|
|
289
|
+
apiUrl="https://api.clarvobot.com/api"
|
|
290
|
+
apiKey="your-key"
|
|
291
|
+
/>
|
|
292
|
+
</>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## 🐛 Error Handling
|
|
298
|
+
|
|
299
|
+
The widget handles common errors gracefully:
|
|
300
|
+
|
|
301
|
+
- ❌ Invalid API key → "Invalid API key. Please check your credentials."
|
|
302
|
+
- ❌ Rate limiting → "Too many requests. Please wait a moment."
|
|
303
|
+
- ❌ Network issues → "Unable to connect to the server."
|
|
304
|
+
- ❌ Server errors → "Server error. Please try again later."
|
|
305
|
+
|
|
306
|
+
All errors are displayed in a user-friendly format within the chat window.
|
|
307
|
+
|
|
308
|
+
## 📦 Bundle Size
|
|
309
|
+
|
|
310
|
+
- **Minified**: ~45KB
|
|
311
|
+
- **Gzipped**: ~15KB
|
|
312
|
+
- **Dependencies**: React, Lucide Icons, Axios
|
|
313
|
+
|
|
314
|
+
## 🔒 Security
|
|
315
|
+
|
|
316
|
+
- ✅ X-API-Key authentication
|
|
317
|
+
- ✅ HTTPS required for production
|
|
318
|
+
- ✅ XSS protection (React's built-in escaping)
|
|
319
|
+
- ✅ CORS handled by your backend
|
|
320
|
+
- ✅ No data stored in localStorage (optional session storage)
|
|
321
|
+
|
|
322
|
+
## 🚀 Deployment
|
|
323
|
+
|
|
324
|
+
### Option 1: Direct Copy (Easiest)
|
|
325
|
+
|
|
326
|
+
Copy the entire `src` folder to your React project:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
cp -r chatbot-widget/src ./src/components/chatbot
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Then import:
|
|
333
|
+
```tsx
|
|
334
|
+
import { ChatbotWidget } from './components/chatbot';
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Option 2: NPM Package
|
|
338
|
+
|
|
339
|
+
Build and publish:
|
|
340
|
+
```bash
|
|
341
|
+
npm run build
|
|
342
|
+
npm publish
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Then install in your projects:
|
|
346
|
+
```bash
|
|
347
|
+
npm install fyrebot-widget
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## 📄 License
|
|
351
|
+
|
|
352
|
+
MIT © [Fyrebot](https://fyrebot.com)
|
|
353
|
+
|
|
354
|
+
## 🤝 Support
|
|
355
|
+
|
|
356
|
+
For issues or questions:
|
|
357
|
+
- 📧 Email: support@fyrebot.com
|
|
358
|
+
- 🌐 Website: [fyrebot.com](https://fyrebot.com)
|
|
359
|
+
- 📚 Docs: [docs.fyrebot.com](https://docs.fyrebot.com)
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
**Made with ❤️ by Fyrebot** - Empowering businesses with AI-powered conversations
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatbot Popup Widget
|
|
3
|
+
* Production-ready, standalone chatbot component
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { ChatbotConfig } from './types';
|
|
7
|
+
export declare const ChatbotWidget: React.FC<ChatbotConfig>;
|
|
8
|
+
export default ChatbotWidget;
|
|
9
|
+
//# sourceMappingURL=ChatbotWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatbotWidget.d.ts","sourceRoot":"","sources":["../src/ChatbotWidget.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,SAAS,CAAC;AAE1D,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA6nBjD,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Chatbot Popup Widget
|
|
4
|
+
* Production-ready, standalone chatbot component
|
|
5
|
+
*/
|
|
6
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
7
|
+
import { MessageCircle, X, Send, Loader2, Bot, User, Sparkles, RefreshCw } from 'lucide-react';
|
|
8
|
+
import { ChatbotApiClient } from './api';
|
|
9
|
+
export const ChatbotWidget = ({ apiUrl, apiKey, title = 'AI Assistant', subtitle = 'Ask me anything', brandName, primaryColor = '#6366f1', welcomeMessage = 'Hello! How can I help you today?', placeholder = 'Type your message...', position = 'bottom-right', showTypingIndicator = true, showTimestamps = true, showSources = true, maxHeight = '600px', maxWidth = '400px', className = '', onOpen, onClose, onMessageSent, onMessageReceived, onError, }) => {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
+
const [messages, setMessages] = useState([]);
|
|
12
|
+
const [input, setInput] = useState('');
|
|
13
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const scrollRef = useRef(null);
|
|
16
|
+
const apiClient = useRef(null);
|
|
17
|
+
// Initialize API client
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
apiClient.current = new ChatbotApiClient(apiUrl, apiKey);
|
|
20
|
+
}, [apiUrl, apiKey]);
|
|
21
|
+
// Auto-scroll to bottom
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (scrollRef.current) {
|
|
24
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
25
|
+
}
|
|
26
|
+
}, [messages, isLoading]);
|
|
27
|
+
// Handle open/close
|
|
28
|
+
const handleToggle = useCallback(() => {
|
|
29
|
+
const newState = !isOpen;
|
|
30
|
+
setIsOpen(newState);
|
|
31
|
+
if (newState) {
|
|
32
|
+
onOpen?.();
|
|
33
|
+
setError(null);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
onClose?.();
|
|
37
|
+
}
|
|
38
|
+
}, [isOpen, onOpen, onClose]);
|
|
39
|
+
// Send message
|
|
40
|
+
const handleSend = useCallback(async () => {
|
|
41
|
+
if (!input.trim() || isLoading || !apiClient.current)
|
|
42
|
+
return;
|
|
43
|
+
const userMessage = {
|
|
44
|
+
id: `user-${Date.now()}`,
|
|
45
|
+
role: 'user',
|
|
46
|
+
content: input.trim(),
|
|
47
|
+
timestamp: new Date(),
|
|
48
|
+
};
|
|
49
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
50
|
+
setInput('');
|
|
51
|
+
setIsLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
onMessageSent?.(userMessage.content);
|
|
54
|
+
try {
|
|
55
|
+
const response = await apiClient.current.sendMessage(userMessage.content);
|
|
56
|
+
const assistantMessage = {
|
|
57
|
+
id: `assistant-${Date.now()}`,
|
|
58
|
+
role: 'assistant',
|
|
59
|
+
content: response.answer,
|
|
60
|
+
timestamp: new Date(),
|
|
61
|
+
sources: response.sources,
|
|
62
|
+
};
|
|
63
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
64
|
+
onMessageReceived?.(assistantMessage);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const error = err;
|
|
68
|
+
setError(error.message);
|
|
69
|
+
onError?.(error);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setIsLoading(false);
|
|
73
|
+
}
|
|
74
|
+
}, [input, isLoading, onMessageSent, onMessageReceived, onError]);
|
|
75
|
+
// Handle Enter key
|
|
76
|
+
const handleKeyPress = useCallback((e) => {
|
|
77
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
handleSend();
|
|
80
|
+
}
|
|
81
|
+
}, [handleSend]);
|
|
82
|
+
// Reset conversation
|
|
83
|
+
const handleReset = useCallback(() => {
|
|
84
|
+
setMessages([]);
|
|
85
|
+
setError(null);
|
|
86
|
+
apiClient.current?.resetSession();
|
|
87
|
+
}, []);
|
|
88
|
+
// Position styles
|
|
89
|
+
const positionStyles = {
|
|
90
|
+
'bottom-right': { bottom: '20px', right: '20px' },
|
|
91
|
+
'bottom-left': { bottom: '20px', left: '20px' },
|
|
92
|
+
'top-right': { top: '20px', right: '20px' },
|
|
93
|
+
'top-left': { top: '20px', left: '20px' },
|
|
94
|
+
};
|
|
95
|
+
return (_jsxs("div", { className: `chatbot-widget-container ${className}`, style: positionStyles[position], children: [_jsx("style", { children: `
|
|
96
|
+
.chatbot-widget-container {
|
|
97
|
+
position: fixed;
|
|
98
|
+
z-index: 9999;
|
|
99
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.chatbot-button {
|
|
103
|
+
width: 60px;
|
|
104
|
+
height: 60px;
|
|
105
|
+
border-radius: 50%;
|
|
106
|
+
background: ${primaryColor};
|
|
107
|
+
border: none;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
transition: all 0.3s ease;
|
|
114
|
+
color: white;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.chatbot-button:hover {
|
|
118
|
+
transform: scale(1.1);
|
|
119
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.chatbot-window {
|
|
123
|
+
position: absolute;
|
|
124
|
+
bottom: 80px;
|
|
125
|
+
right: 0;
|
|
126
|
+
width: ${maxWidth};
|
|
127
|
+
max-width: calc(100vw - 40px);
|
|
128
|
+
height: ${maxHeight};
|
|
129
|
+
max-height: calc(100vh - 120px);
|
|
130
|
+
background: #1a1a1a;
|
|
131
|
+
border-radius: 16px;
|
|
132
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
animation: slideUp 0.3s ease;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@keyframes slideUp {
|
|
140
|
+
from {
|
|
141
|
+
opacity: 0;
|
|
142
|
+
transform: translateY(20px);
|
|
143
|
+
}
|
|
144
|
+
to {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
transform: translateY(0);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.chatbot-header {
|
|
151
|
+
background: linear-gradient(135deg, ${primaryColor}, ${primaryColor}dd);
|
|
152
|
+
padding: 16px;
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
color: white;
|
|
157
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.chatbot-header-content {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 12px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.chatbot-icon {
|
|
167
|
+
width: 36px;
|
|
168
|
+
height: 36px;
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
background: rgba(255, 255, 255, 0.2);
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.chatbot-title {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.chatbot-title h3 {
|
|
182
|
+
margin: 0;
|
|
183
|
+
font-size: 16px;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.chatbot-title p {
|
|
188
|
+
margin: 0;
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
opacity: 0.9;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.chatbot-close {
|
|
194
|
+
background: none;
|
|
195
|
+
border: none;
|
|
196
|
+
color: white;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
padding: 8px;
|
|
199
|
+
border-radius: 8px;
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
transition: background 0.2s;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.chatbot-close:hover {
|
|
207
|
+
background: rgba(255, 255, 255, 0.1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.chatbot-messages {
|
|
211
|
+
flex: 1;
|
|
212
|
+
overflow-y: auto;
|
|
213
|
+
padding: 16px;
|
|
214
|
+
background: #1a1a1a;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.chatbot-messages::-webkit-scrollbar {
|
|
218
|
+
width: 8px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.chatbot-messages::-webkit-scrollbar-track {
|
|
222
|
+
background: #2a2a2a;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.chatbot-messages::-webkit-scrollbar-thumb {
|
|
226
|
+
background: #444;
|
|
227
|
+
border-radius: 4px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.chatbot-welcome {
|
|
231
|
+
text-align: center;
|
|
232
|
+
padding: 40px 20px;
|
|
233
|
+
color: #888;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.chatbot-welcome-icon {
|
|
237
|
+
width: 64px;
|
|
238
|
+
height: 64px;
|
|
239
|
+
margin: 0 auto 16px;
|
|
240
|
+
border-radius: 50%;
|
|
241
|
+
background: rgba(99, 102, 241, 0.1);
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: center;
|
|
245
|
+
color: ${primaryColor};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.chatbot-message {
|
|
249
|
+
display: flex;
|
|
250
|
+
gap: 12px;
|
|
251
|
+
margin-bottom: 16px;
|
|
252
|
+
animation: fadeIn 0.3s ease;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@keyframes fadeIn {
|
|
256
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
257
|
+
to { opacity: 1; transform: translateY(0); }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.chatbot-message.user {
|
|
261
|
+
flex-direction: row-reverse;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.chatbot-avatar {
|
|
265
|
+
width: 32px;
|
|
266
|
+
height: 32px;
|
|
267
|
+
border-radius: 50%;
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
justify-content: center;
|
|
271
|
+
flex-shrink: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.chatbot-avatar.bot {
|
|
275
|
+
background: ${primaryColor};
|
|
276
|
+
color: white;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.chatbot-avatar.user {
|
|
280
|
+
background: #333;
|
|
281
|
+
color: #aaa;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.chatbot-message-content {
|
|
285
|
+
flex: 1;
|
|
286
|
+
max-width: 75%;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.chatbot-message.user .chatbot-message-content {
|
|
290
|
+
display: flex;
|
|
291
|
+
flex-direction: column;
|
|
292
|
+
align-items: flex-end;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.chatbot-bubble {
|
|
296
|
+
padding: 12px 16px;
|
|
297
|
+
border-radius: 12px;
|
|
298
|
+
word-wrap: break-word;
|
|
299
|
+
line-height: 1.5;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.chatbot-bubble.bot {
|
|
303
|
+
background: #2a2a2a;
|
|
304
|
+
color: #e0e0e0;
|
|
305
|
+
border-bottom-left-radius: 4px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.chatbot-bubble.user {
|
|
309
|
+
background: ${primaryColor};
|
|
310
|
+
color: white;
|
|
311
|
+
border-bottom-right-radius: 4px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.chatbot-timestamp {
|
|
315
|
+
font-size: 11px;
|
|
316
|
+
color: #666;
|
|
317
|
+
margin-top: 4px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.chatbot-sources {
|
|
321
|
+
margin-top: 8px;
|
|
322
|
+
display: flex;
|
|
323
|
+
flex-wrap: wrap;
|
|
324
|
+
gap: 6px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.chatbot-source {
|
|
328
|
+
font-size: 11px;
|
|
329
|
+
padding: 4px 8px;
|
|
330
|
+
background: rgba(99, 102, 241, 0.1);
|
|
331
|
+
color: ${primaryColor};
|
|
332
|
+
border-radius: 4px;
|
|
333
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.chatbot-typing {
|
|
337
|
+
display: flex;
|
|
338
|
+
gap: 12px;
|
|
339
|
+
align-items: center;
|
|
340
|
+
animation: fadeIn 0.3s ease;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.chatbot-typing-dots {
|
|
344
|
+
display: flex;
|
|
345
|
+
gap: 4px;
|
|
346
|
+
padding: 12px 16px;
|
|
347
|
+
background: #2a2a2a;
|
|
348
|
+
border-radius: 12px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.chatbot-typing-dot {
|
|
352
|
+
width: 8px;
|
|
353
|
+
height: 8px;
|
|
354
|
+
border-radius: 50%;
|
|
355
|
+
background: #666;
|
|
356
|
+
animation: typing 1.4s infinite;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.chatbot-typing-dot:nth-child(2) {
|
|
360
|
+
animation-delay: 0.2s;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.chatbot-typing-dot:nth-child(3) {
|
|
364
|
+
animation-delay: 0.4s;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
@keyframes typing {
|
|
368
|
+
0%, 60%, 100% {
|
|
369
|
+
transform: translateY(0);
|
|
370
|
+
opacity: 0.4;
|
|
371
|
+
}
|
|
372
|
+
30% {
|
|
373
|
+
transform: translateY(-10px);
|
|
374
|
+
opacity: 1;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.chatbot-error {
|
|
379
|
+
background: rgba(239, 68, 68, 0.1);
|
|
380
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
381
|
+
color: #ef4444;
|
|
382
|
+
padding: 12px;
|
|
383
|
+
border-radius: 8px;
|
|
384
|
+
margin: 0 16px 16px;
|
|
385
|
+
font-size: 13px;
|
|
386
|
+
display: flex;
|
|
387
|
+
align-items: center;
|
|
388
|
+
gap: 8px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.chatbot-input-area {
|
|
392
|
+
padding: 16px;
|
|
393
|
+
background: #222;
|
|
394
|
+
border-top: 1px solid #333;
|
|
395
|
+
display: flex;
|
|
396
|
+
gap: 8px;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.chatbot-input-wrapper {
|
|
400
|
+
flex: 1;
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
background: #2a2a2a;
|
|
404
|
+
border-radius: 12px;
|
|
405
|
+
padding: 0 12px;
|
|
406
|
+
border: 1px solid #333;
|
|
407
|
+
transition: border-color 0.2s;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.chatbot-input-wrapper:focus-within {
|
|
411
|
+
border-color: ${primaryColor};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.chatbot-input {
|
|
415
|
+
flex: 1;
|
|
416
|
+
background: none;
|
|
417
|
+
border: none;
|
|
418
|
+
outline: none;
|
|
419
|
+
padding: 12px 0;
|
|
420
|
+
color: #e0e0e0;
|
|
421
|
+
font-size: 14px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.chatbot-input::placeholder {
|
|
425
|
+
color: #666;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.chatbot-button-icon {
|
|
429
|
+
background: ${primaryColor};
|
|
430
|
+
border: none;
|
|
431
|
+
color: white;
|
|
432
|
+
width: 40px;
|
|
433
|
+
height: 40px;
|
|
434
|
+
border-radius: 10px;
|
|
435
|
+
display: flex;
|
|
436
|
+
align-items: center;
|
|
437
|
+
justify-content: center;
|
|
438
|
+
cursor: pointer;
|
|
439
|
+
transition: all 0.2s;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.chatbot-button-icon:hover:not(:disabled) {
|
|
443
|
+
opacity: 0.9;
|
|
444
|
+
transform: scale(1.05);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.chatbot-button-icon:disabled {
|
|
448
|
+
opacity: 0.5;
|
|
449
|
+
cursor: not-allowed;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.chatbot-reset {
|
|
453
|
+
background: #2a2a2a;
|
|
454
|
+
border: 1px solid #333;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.chatbot-reset:hover:not(:disabled) {
|
|
458
|
+
background: #333;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@media (max-width: 480px) {
|
|
462
|
+
.chatbot-window {
|
|
463
|
+
width: calc(100vw - 40px);
|
|
464
|
+
height: calc(100vh - 120px);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
` }), !isOpen && (_jsx("button", { className: "chatbot-button", onClick: handleToggle, "aria-label": "Open chat", children: _jsx(MessageCircle, { size: 28 }) })), isOpen && (_jsxs("div", { className: "chatbot-window", children: [_jsxs("div", { className: "chatbot-header", children: [_jsxs("div", { className: "chatbot-header-content", children: [_jsx("div", { className: "chatbot-icon", children: _jsx(Sparkles, { size: 20 }) }), _jsxs("div", { className: "chatbot-title", children: [_jsx("h3", { children: title }), subtitle && _jsx("p", { children: subtitle })] })] }), _jsxs("div", { style: { display: 'flex', gap: '8px' }, children: [messages.length > 0 && (_jsx("button", { className: "chatbot-close", onClick: handleReset, "aria-label": "Reset conversation", title: "Start new conversation", children: _jsx(RefreshCw, { size: 18 }) })), _jsx("button", { className: "chatbot-close", onClick: handleToggle, "aria-label": "Close chat", children: _jsx(X, { size: 20 }) })] })] }), _jsxs("div", { className: "chatbot-messages", ref: scrollRef, children: [messages.length === 0 ? (_jsxs("div", { className: "chatbot-welcome", children: [_jsx("div", { className: "chatbot-welcome-icon", children: _jsx(Bot, { size: 32 }) }), _jsx("h4", { style: { margin: '0 0 8px 0', color: '#ccc' }, children: brandName ? `Welcome to ${brandName}!` : 'Welcome!' }), _jsx("p", { style: { margin: 0, fontSize: '14px' }, children: welcomeMessage })] })) : (messages.map((message) => (_jsxs("div", { className: `chatbot-message ${message.role}`, children: [_jsx("div", { className: `chatbot-avatar ${message.role === 'user' ? 'user' : 'bot'}`, children: message.role === 'user' ? _jsx(User, { size: 18 }) : _jsx(Bot, { size: 18 }) }), _jsxs("div", { className: "chatbot-message-content", children: [_jsx("div", { className: `chatbot-bubble ${message.role === 'user' ? 'user' : 'bot'}`, children: message.content }), showSources && message.sources && message.sources.length > 0 && (_jsx("div", { className: "chatbot-sources", children: message.sources.map((source, idx) => (_jsxs("span", { className: "chatbot-source", children: [source.title, " (", Math.round(source.score * 100), "%)"] }, idx))) })), showTimestamps && (_jsx("div", { className: "chatbot-timestamp", children: new Date(message.timestamp).toLocaleTimeString([], {
|
|
468
|
+
hour: '2-digit',
|
|
469
|
+
minute: '2-digit',
|
|
470
|
+
}) }))] })] }, message.id)))), isLoading && showTypingIndicator && (_jsxs("div", { className: "chatbot-typing", children: [_jsx("div", { className: "chatbot-avatar bot", children: _jsx(Bot, { size: 18 }) }), _jsxs("div", { className: "chatbot-typing-dots", children: [_jsx("div", { className: "chatbot-typing-dot" }), _jsx("div", { className: "chatbot-typing-dot" }), _jsx("div", { className: "chatbot-typing-dot" })] })] }))] }), error && (_jsxs("div", { className: "chatbot-error", children: [_jsx("span", { children: "\u26A0\uFE0F" }), error] })), _jsxs("div", { className: "chatbot-input-area", children: [_jsx("div", { className: "chatbot-input-wrapper", children: _jsx("input", { type: "text", className: "chatbot-input", value: input, onChange: (e) => setInput(e.target.value), onKeyPress: handleKeyPress, placeholder: placeholder, disabled: isLoading }) }), _jsx("button", { className: "chatbot-button-icon", onClick: handleSend, disabled: !input.trim() || isLoading, "aria-label": "Send message", children: isLoading ? _jsx(Loader2, { size: 20, className: "spin" }) : _jsx(Send, { size: 20 }) })] })] })), _jsx("style", { children: `
|
|
471
|
+
.spin {
|
|
472
|
+
animation: spin 1s line <s }
|
|
473
|
+
@keyframes spin {
|
|
474
|
+
from { transform: rotate(0deg); }
|
|
475
|
+
to { transform: rotate(360deg); }
|
|
476
|
+
}
|
|
477
|
+
` })] }));
|
|
478
|
+
};
|
|
479
|
+
export default ChatbotWidget;
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for Chatbot Widget
|
|
3
|
+
* Handles all communication
|
|
4
|
+
with the backend using X-API-Key authentication
|
|
5
|
+
*/
|
|
6
|
+
import type { ChatApiResponse } from './types';
|
|
7
|
+
export declare class ChatbotApiClient {
|
|
8
|
+
private client;
|
|
9
|
+
private apiKey;
|
|
10
|
+
private sessionId;
|
|
11
|
+
constructor(apiUrl: string, apiKey: string);
|
|
12
|
+
/**
|
|
13
|
+
* Send a chat message to the API
|
|
14
|
+
*/
|
|
15
|
+
sendMessage(message: string): Promise<ChatApiResponse>;
|
|
16
|
+
/**
|
|
17
|
+
* Get current session ID
|
|
18
|
+
*/
|
|
19
|
+
getSessionId(): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Reset session (start new conversation)
|
|
22
|
+
*/
|
|
23
|
+
resetSession(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Update API key
|
|
26
|
+
*/
|
|
27
|
+
updateApiKey(apiKey: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Handle API errors with user-friendly messages
|
|
30
|
+
*/
|
|
31
|
+
private handleError;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,SAAS,CAAC;AAE5D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;gBAE5B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAqB1C;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA6B5D;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKlC;;OAEG;IACH,OAAO,CAAC,WAAW;CA2BpB"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for Chatbot Widget
|
|
3
|
+
* Handles all communication
|
|
4
|
+
with the backend using X-API-Key authentication
|
|
5
|
+
*/
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
export class ChatbotApiClient {
|
|
8
|
+
constructor(apiUrl, apiKey) {
|
|
9
|
+
this.sessionId = null;
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.client = axios.create({
|
|
12
|
+
baseURL: apiUrl,
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
'X-API-Key': apiKey,
|
|
16
|
+
},
|
|
17
|
+
timeout: 30000, // 30 second timeout
|
|
18
|
+
});
|
|
19
|
+
// Response interceptor for error handling
|
|
20
|
+
this.client.interceptors.response.use((response) => response, (error) => {
|
|
21
|
+
return Promise.reject(this.handleError(error));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Send a chat message to the API
|
|
26
|
+
*/
|
|
27
|
+
async sendMessage(message) {
|
|
28
|
+
try {
|
|
29
|
+
// Build request body - only include sessionId if it exists
|
|
30
|
+
const requestBody = {
|
|
31
|
+
query: message,
|
|
32
|
+
};
|
|
33
|
+
// Only add sessionId if we have one (don't send null or undefined)
|
|
34
|
+
if (this.sessionId) {
|
|
35
|
+
requestBody.sessionId = this.sessionId;
|
|
36
|
+
}
|
|
37
|
+
const response = await this.client.post('/chat', requestBody);
|
|
38
|
+
// Store session ID for conversation continuity
|
|
39
|
+
if (response.data.data?.sessionId) {
|
|
40
|
+
this.sessionId = response.data.data.sessionId;
|
|
41
|
+
}
|
|
42
|
+
if (!response.data.success || !response.data.data) {
|
|
43
|
+
throw new Error(response.data.error || 'Failed to get response');
|
|
44
|
+
}
|
|
45
|
+
return response.data.data;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw this.handleError(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get current session ID
|
|
53
|
+
*/
|
|
54
|
+
getSessionId() {
|
|
55
|
+
return this.sessionId;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reset session (start new conversation)
|
|
59
|
+
*/
|
|
60
|
+
resetSession() {
|
|
61
|
+
this.sessionId = null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update API key
|
|
65
|
+
*/
|
|
66
|
+
updateApiKey(apiKey) {
|
|
67
|
+
this.apiKey = apiKey;
|
|
68
|
+
this.client.defaults.headers['X-API-Key'] = apiKey;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Handle API errors with user-friendly messages
|
|
72
|
+
*/
|
|
73
|
+
handleError(error) {
|
|
74
|
+
if (axios.isAxiosError(error)) {
|
|
75
|
+
const axiosError = error;
|
|
76
|
+
if (axiosError.response) {
|
|
77
|
+
const status = axiosError.response.status;
|
|
78
|
+
const data = axiosError.response.data;
|
|
79
|
+
switch (status) {
|
|
80
|
+
case 401:
|
|
81
|
+
return new Error('Invalid API key. Please check your credentials.');
|
|
82
|
+
case 403:
|
|
83
|
+
return new Error('Access denied. You may have exceeded your usage limits.');
|
|
84
|
+
case 429:
|
|
85
|
+
return new Error('Too many requests. Please wait a moment and try again.');
|
|
86
|
+
case 500:
|
|
87
|
+
return new Error('Server error. Please try again later.');
|
|
88
|
+
default:
|
|
89
|
+
return new Error(data?.error || data?.message || 'An unexpected error occurred.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (axiosError.request) {
|
|
93
|
+
return new Error('Unable to connect to the server. Please check your internet connection.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return error instanceof Error ? error : new Error('An unexpected error occurred.');
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatbot Widget - Main Entry Point
|
|
3
|
+
* Export all public APIs
|
|
4
|
+
*/
|
|
5
|
+
export { ChatbotWidget } from './ChatbotWidget';
|
|
6
|
+
export { ChatbotApiClient } from './api';
|
|
7
|
+
export type { ChatbotConfig, ChatMessage, ChatSession, ApiResponse, ChatApiResponse } from './types';
|
|
8
|
+
export { ChatbotWidget as default } from './ChatbotWidget';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,YAAY,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EAChB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatbot Widget Types
|
|
3
|
+
* Production-ready TypeScript definitions
|
|
4
|
+
*/
|
|
5
|
+
export interface ChatMessage {
|
|
6
|
+
id: string;
|
|
7
|
+
role: 'user' | 'assistant';
|
|
8
|
+
content: string;
|
|
9
|
+
timestamp: Date;
|
|
10
|
+
sources?: Array<{
|
|
11
|
+
title: string;
|
|
12
|
+
score: number;
|
|
13
|
+
content?: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export interface ChatbotConfig {
|
|
17
|
+
/** API endpoint URL (e.g., 'https://api.example.com') */
|
|
18
|
+
apiUrl: string;
|
|
19
|
+
/** X-API-Key for authentication */
|
|
20
|
+
apiKey: string;
|
|
21
|
+
/** Title displayed in the chatbot header */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Subtitle or description */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
/** Brand name for personalization */
|
|
26
|
+
brandName?: string;
|
|
27
|
+
/** Primary color theme (hex color) */
|
|
28
|
+
primaryColor?: string;
|
|
29
|
+
/** Welcome message shown when chat is empty */
|
|
30
|
+
welcomeMessage?: string;
|
|
31
|
+
/** Placeholder text for input field */
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
/** Position of the chat button */
|
|
34
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
35
|
+
/** Enable/disable typing indicators */
|
|
36
|
+
showTypingIndicator?: boolean;
|
|
37
|
+
/** Enable/disable timestamps */
|
|
38
|
+
showTimestamps?: boolean;
|
|
39
|
+
/** Enable/disable source citations */
|
|
40
|
+
showSources?: boolean;
|
|
41
|
+
/** Maximum height of the chat window */
|
|
42
|
+
maxHeight?: string;
|
|
43
|
+
/** Maximum width of the chat window */
|
|
44
|
+
maxWidth?: string;
|
|
45
|
+
/** Custom CSS class for styling */
|
|
46
|
+
className?: string;
|
|
47
|
+
/** Callback when chat is opened */
|
|
48
|
+
onOpen?: () => void;
|
|
49
|
+
/** Callback when chat is closed */
|
|
50
|
+
onClose?: () => void;
|
|
51
|
+
/** Callback when message is sent */
|
|
52
|
+
onMessageSent?: (message: string) => void;
|
|
53
|
+
/** Callback when response is received */
|
|
54
|
+
onMessageReceived?: (message: ChatMessage) => void;
|
|
55
|
+
/** Callback when error occurs */
|
|
56
|
+
onError?: (error: Error) => void;
|
|
57
|
+
}
|
|
58
|
+
export interface ChatSession {
|
|
59
|
+
sessionId: string;
|
|
60
|
+
messages: ChatMessage[];
|
|
61
|
+
createdAt: Date;
|
|
62
|
+
updatedAt: Date;
|
|
63
|
+
}
|
|
64
|
+
export interface ApiResponse<T = any> {
|
|
65
|
+
success: boolean;
|
|
66
|
+
data?: T;
|
|
67
|
+
error?: string;
|
|
68
|
+
message?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ChatApiResponse {
|
|
71
|
+
answer: string;
|
|
72
|
+
sessionId: string;
|
|
73
|
+
sources?: Array<{
|
|
74
|
+
title: string;
|
|
75
|
+
score: number;
|
|
76
|
+
content?: string;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IAEf,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IAEf,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IAErE,uCAAuC;IACvC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B,gCAAgC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAEnD,iCAAiC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fyrebot-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready AI chatbot popup widget by Fyrebot - Multi-tenant support with seamless React integration",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
26
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"fyrebot",
|
|
30
|
+
"chatbot",
|
|
31
|
+
"ai",
|
|
32
|
+
"widget",
|
|
33
|
+
"popup",
|
|
34
|
+
"react",
|
|
35
|
+
"typescript",
|
|
36
|
+
"multi-tenant",
|
|
37
|
+
"saas",
|
|
38
|
+
"customer-support",
|
|
39
|
+
"chat-widget",
|
|
40
|
+
"ai-assistant",
|
|
41
|
+
"embedded-chat",
|
|
42
|
+
"live-chat"
|
|
43
|
+
],
|
|
44
|
+
"author": "Fyrebot <support@fyrebot.com>",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": ">=16.8.0",
|
|
48
|
+
"react-dom": ">=16.8.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"axios": "^1.7.0",
|
|
52
|
+
"lucide-react": "^0.460.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/react": "^18.3.0",
|
|
56
|
+
"@types/react-dom": "^18.3.0",
|
|
57
|
+
"typescript": "^5.6.0"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "https://github.com/fyrebot/chatbot-widget.git"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/fyrebot/chatbot-widget/issues"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://fyrebot.com"
|
|
67
|
+
}
|