floating-copilot-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 +231 -0
- package/dist/FloatingCopilot.css +265 -0
- package/dist/FloatingCopilot.d.ts +8 -0
- package/dist/FloatingCopilot.js +208 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +231 -0
- package/dist/index.js +237 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +22 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Floating Copilot Contributors
|
|
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,231 @@
|
|
|
1
|
+
# Floating Copilot Widget
|
|
2
|
+
|
|
3
|
+
A lightweight, highly configurable floating chat widget plugin for React websites. Drop it into any React application and get an instantly customizable chat interface that can be dragged, resized, minimized, and maximized.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✨ **Easy Integration** - Simply import and use the component
|
|
8
|
+
🎨 **Fully Customizable** - Configure colors, position, dimensions, and more
|
|
9
|
+
🖱️ **Draggable & Resizable** - Users can move and resize the widget
|
|
10
|
+
📱 **Responsive** - Adapts to mobile screens automatically
|
|
11
|
+
♿ **Accessible** - Keyboard shortcuts and semantic HTML
|
|
12
|
+
🎯 **Feature Rich** - Minimize, maximize, close buttons with callbacks
|
|
13
|
+
💬 **Chat Interface** - Built-in message display with auto-scroll
|
|
14
|
+
⚡ **Performance** - Lightweight with zero external dependencies
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install floating-copilot-widget
|
|
20
|
+
# or
|
|
21
|
+
yarn add floating-copilot-widget
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
import React from 'react';
|
|
28
|
+
import FloatingCopilot from 'floating-copilot-widget';
|
|
29
|
+
import 'floating-copilot-widget/dist/FloatingCopilot.css';
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<FloatingCopilot
|
|
35
|
+
config={{
|
|
36
|
+
title: 'Chat Assistant',
|
|
37
|
+
position: 'bottom-right',
|
|
38
|
+
onMessage: (message) => console.log('User said:', message),
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default App;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
Pass a `config` object to customize the widget:
|
|
51
|
+
|
|
52
|
+
```jsx
|
|
53
|
+
<FloatingCopilot
|
|
54
|
+
config={{
|
|
55
|
+
// Positioning
|
|
56
|
+
position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
57
|
+
|
|
58
|
+
// Dimensions
|
|
59
|
+
width: 350,
|
|
60
|
+
height: 500,
|
|
61
|
+
|
|
62
|
+
// Features
|
|
63
|
+
draggable: true,
|
|
64
|
+
resizable: true,
|
|
65
|
+
|
|
66
|
+
// Appearance
|
|
67
|
+
title: 'Chat Assistant',
|
|
68
|
+
placeholder: 'Type your message...',
|
|
69
|
+
theme: {
|
|
70
|
+
primaryColor: '#6366f1',
|
|
71
|
+
backgroundColor: '#ffffff',
|
|
72
|
+
textColor: '#1f2937',
|
|
73
|
+
borderColor: '#e5e7eb',
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// State
|
|
77
|
+
minimized: false,
|
|
78
|
+
maximized: false,
|
|
79
|
+
|
|
80
|
+
// Controls
|
|
81
|
+
showHeader: true,
|
|
82
|
+
showMinimizeBtn: true,
|
|
83
|
+
showMaximizeBtn: true,
|
|
84
|
+
showCloseBtn: true,
|
|
85
|
+
|
|
86
|
+
// Layering
|
|
87
|
+
zIndex: 9999,
|
|
88
|
+
|
|
89
|
+
// Callbacks
|
|
90
|
+
onMessage: (message) => handleMessage(message),
|
|
91
|
+
onClose: () => handleClose(),
|
|
92
|
+
onMinimize: () => handleMinimize(),
|
|
93
|
+
onMaximize: () => handleMaximize(),
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration Options
|
|
99
|
+
|
|
100
|
+
### Position
|
|
101
|
+
- `bottom-right` (default)
|
|
102
|
+
- `bottom-left`
|
|
103
|
+
- `top-right`
|
|
104
|
+
- `top-left`
|
|
105
|
+
|
|
106
|
+
### Theme Colors
|
|
107
|
+
Customize the appearance with your brand colors:
|
|
108
|
+
```jsx
|
|
109
|
+
theme: {
|
|
110
|
+
primaryColor: '#6366f1', // Button, user message background
|
|
111
|
+
backgroundColor: '#ffffff', // Widget background
|
|
112
|
+
textColor: '#1f2937', // Text color
|
|
113
|
+
borderColor: '#e5e7eb', // Border color
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Callbacks
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
// Called when user sends a message
|
|
121
|
+
onMessage: (message: string) => void
|
|
122
|
+
|
|
123
|
+
// Called when close button is clicked
|
|
124
|
+
onClose: () => void
|
|
125
|
+
|
|
126
|
+
// Called when minimize button is clicked
|
|
127
|
+
onMinimize: () => void
|
|
128
|
+
|
|
129
|
+
// Called when maximize button is clicked
|
|
130
|
+
onMaximize: () => void
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Advanced Usage
|
|
134
|
+
|
|
135
|
+
### Custom Message Handler
|
|
136
|
+
|
|
137
|
+
```jsx
|
|
138
|
+
<FloatingCopilot
|
|
139
|
+
config={{
|
|
140
|
+
onMessage: async (message) => {
|
|
141
|
+
// Send to your API
|
|
142
|
+
const response = await fetch('/api/chat', {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
body: JSON.stringify({ message }),
|
|
145
|
+
});
|
|
146
|
+
// Handle response...
|
|
147
|
+
},
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Conditional Rendering
|
|
153
|
+
|
|
154
|
+
```jsx
|
|
155
|
+
const [showChat, setShowChat] = useState(true);
|
|
156
|
+
|
|
157
|
+
return showChat ? (
|
|
158
|
+
<FloatingCopilot
|
|
159
|
+
config={{
|
|
160
|
+
onClose: () => setShowChat(false),
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
) : null;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Multiple Instances
|
|
167
|
+
|
|
168
|
+
```jsx
|
|
169
|
+
<FloatingCopilot
|
|
170
|
+
config={{
|
|
171
|
+
position: 'bottom-right',
|
|
172
|
+
title: 'Chat',
|
|
173
|
+
zIndex: 9999,
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
|
|
177
|
+
<FloatingCopilot
|
|
178
|
+
config={{
|
|
179
|
+
position: 'bottom-left',
|
|
180
|
+
title: 'Support',
|
|
181
|
+
zIndex: 9998,
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Browser Support
|
|
187
|
+
|
|
188
|
+
- Chrome (latest)
|
|
189
|
+
- Firefox (latest)
|
|
190
|
+
- Safari (latest)
|
|
191
|
+
- Edge (latest)
|
|
192
|
+
|
|
193
|
+
## Styling
|
|
194
|
+
|
|
195
|
+
The widget uses CSS variables for theming. You can override them globally:
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
:root {
|
|
199
|
+
--primary-color: #6366f1;
|
|
200
|
+
--bg-color: #ffffff;
|
|
201
|
+
--text-color: #1f2937;
|
|
202
|
+
--border-color: #e5e7eb;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## TypeScript Support
|
|
207
|
+
|
|
208
|
+
Full TypeScript support with exported types:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { FloatingCopilot, FloatingCopilotConfig } from 'floating-copilot-widget';
|
|
212
|
+
|
|
213
|
+
const config: FloatingCopilotConfig = {
|
|
214
|
+
position: 'bottom-right',
|
|
215
|
+
// ... type-safe configuration
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
<FloatingCopilot config={config} />
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
224
|
+
|
|
225
|
+
## Contributing
|
|
226
|
+
|
|
227
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
228
|
+
|
|
229
|
+
## Support
|
|
230
|
+
|
|
231
|
+
For issues or questions, please open an issue on the GitHub repository.
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/* Floating Copilot Widget Styles */
|
|
2
|
+
|
|
3
|
+
.floating-copilot {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
border-radius: 8px;
|
|
7
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
9
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
10
|
+
sans-serif;
|
|
11
|
+
background-color: var(--bg-color, #ffffff);
|
|
12
|
+
color: var(--text-color, #1f2937);
|
|
13
|
+
border: 1px solid var(--border-color, #e5e7eb);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
transition: all 0.3s ease;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.floating-copilot.maximized {
|
|
19
|
+
border-radius: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.floating-copilot.minimized {
|
|
23
|
+
height: auto !important;
|
|
24
|
+
width: auto !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Header */
|
|
28
|
+
.copilot-header {
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
align-items: center;
|
|
32
|
+
padding: 12px 16px;
|
|
33
|
+
background: linear-gradient(135deg, var(--primary-color, #6366f1) 0%, #4f46e5 100%);
|
|
34
|
+
color: white;
|
|
35
|
+
border-bottom: 1px solid var(--border-color, #e5e7eb);
|
|
36
|
+
user-select: none;
|
|
37
|
+
cursor: grab;
|
|
38
|
+
transition: all 0.2s ease;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.copilot-header:active {
|
|
42
|
+
cursor: grabbing;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.copilot-title {
|
|
46
|
+
margin: 0;
|
|
47
|
+
font-size: 16px;
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
flex: 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.copilot-buttons {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 8px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.copilot-btn {
|
|
58
|
+
background: rgba(255, 255, 255, 0.2);
|
|
59
|
+
border: none;
|
|
60
|
+
color: white;
|
|
61
|
+
width: 28px;
|
|
62
|
+
height: 28px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
transition: all 0.2s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.copilot-btn:hover {
|
|
73
|
+
background: rgba(255, 255, 255, 0.3);
|
|
74
|
+
transform: scale(1.05);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.copilot-btn:active {
|
|
78
|
+
transform: scale(0.95);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Messages Container */
|
|
82
|
+
.copilot-messages {
|
|
83
|
+
flex: 1;
|
|
84
|
+
overflow-y: auto;
|
|
85
|
+
padding: 16px;
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
background: var(--bg-color, #ffffff);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.copilot-empty-state {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
height: 100%;
|
|
97
|
+
color: #9ca3af;
|
|
98
|
+
text-align: center;
|
|
99
|
+
font-size: 14px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Message Styles */
|
|
103
|
+
.copilot-message {
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
gap: 4px;
|
|
107
|
+
animation: slideIn 0.3s ease;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@keyframes slideIn {
|
|
111
|
+
from {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
transform: translateY(10px);
|
|
114
|
+
}
|
|
115
|
+
to {
|
|
116
|
+
opacity: 1;
|
|
117
|
+
transform: translateY(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.copilot-message-user {
|
|
122
|
+
align-items: flex-end;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.copilot-message-bot {
|
|
126
|
+
align-items: flex-start;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.copilot-message-content {
|
|
130
|
+
padding: 10px 12px;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
max-width: 85%;
|
|
133
|
+
word-wrap: break-word;
|
|
134
|
+
line-height: 1.4;
|
|
135
|
+
font-size: 14px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.copilot-message-user .copilot-message-content {
|
|
139
|
+
background: var(--primary-color, #6366f1);
|
|
140
|
+
color: white;
|
|
141
|
+
border-radius: 16px 4px 16px 16px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.copilot-message-bot .copilot-message-content {
|
|
145
|
+
background: #f3f4f6;
|
|
146
|
+
color: var(--text-color, #1f2937);
|
|
147
|
+
border-radius: 4px 16px 16px 16px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.copilot-message-time {
|
|
151
|
+
font-size: 11px;
|
|
152
|
+
color: #9ca3af;
|
|
153
|
+
padding: 0 4px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Input Area */
|
|
157
|
+
.copilot-input-area {
|
|
158
|
+
display: flex;
|
|
159
|
+
gap: 8px;
|
|
160
|
+
padding: 12px;
|
|
161
|
+
border-top: 1px solid var(--border-color, #e5e7eb);
|
|
162
|
+
background: var(--bg-color, #ffffff);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.copilot-input {
|
|
166
|
+
flex: 1;
|
|
167
|
+
padding: 10px 12px;
|
|
168
|
+
border: 1px solid var(--border-color, #e5e7eb);
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
font-size: 14px;
|
|
171
|
+
font-family: inherit;
|
|
172
|
+
color: var(--text-color, #1f2937);
|
|
173
|
+
background: #f9fafb;
|
|
174
|
+
transition: all 0.2s ease;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.copilot-input:focus {
|
|
178
|
+
outline: none;
|
|
179
|
+
border-color: var(--primary-color, #6366f1);
|
|
180
|
+
background: white;
|
|
181
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.copilot-input::placeholder {
|
|
185
|
+
color: #d1d5db;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.copilot-send-btn {
|
|
189
|
+
padding: 10px 16px;
|
|
190
|
+
background: var(--primary-color, #6366f1);
|
|
191
|
+
color: white;
|
|
192
|
+
border: none;
|
|
193
|
+
border-radius: 6px;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
font-weight: 500;
|
|
196
|
+
font-size: 14px;
|
|
197
|
+
transition: all 0.2s ease;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.copilot-send-btn:hover {
|
|
201
|
+
background: #4f46e5;
|
|
202
|
+
transform: translateY(-1px);
|
|
203
|
+
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.copilot-send-btn:active {
|
|
207
|
+
transform: translateY(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Resize Handle */
|
|
211
|
+
.copilot-resize-handle {
|
|
212
|
+
position: absolute;
|
|
213
|
+
bottom: 0;
|
|
214
|
+
right: 0;
|
|
215
|
+
width: 20px;
|
|
216
|
+
height: 20px;
|
|
217
|
+
cursor: nwse-resize;
|
|
218
|
+
background: linear-gradient(135deg, transparent 50%, var(--primary-color, #6366f1) 50%);
|
|
219
|
+
border-radius: 0 0 8px 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.copilot-resize-handle:hover {
|
|
223
|
+
background: linear-gradient(135deg, transparent 50%, #4f46e5 50%);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Scrollbar Styling */
|
|
227
|
+
.copilot-messages::-webkit-scrollbar {
|
|
228
|
+
width: 6px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.copilot-messages::-webkit-scrollbar-track {
|
|
232
|
+
background: #f3f4f6;
|
|
233
|
+
border-radius: 10px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.copilot-messages::-webkit-scrollbar-thumb {
|
|
237
|
+
background: #d1d5db;
|
|
238
|
+
border-radius: 10px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.copilot-messages::-webkit-scrollbar-thumb:hover {
|
|
242
|
+
background: #9ca3af;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Responsive Design */
|
|
246
|
+
@media (max-width: 480px) {
|
|
247
|
+
.floating-copilot {
|
|
248
|
+
width: 100vw !important;
|
|
249
|
+
height: 100vh !important;
|
|
250
|
+
border-radius: 0;
|
|
251
|
+
position: fixed !important;
|
|
252
|
+
top: 0 !important;
|
|
253
|
+
left: 0 !important;
|
|
254
|
+
right: 0 !important;
|
|
255
|
+
bottom: 0 !important;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.copilot-message-content {
|
|
259
|
+
max-width: 90%;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.copilot-resize-handle {
|
|
263
|
+
display: none;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FloatingCopilotConfig } from './types';
|
|
3
|
+
import './FloatingCopilot.css';
|
|
4
|
+
interface FloatingCopilotProps {
|
|
5
|
+
config?: Partial<FloatingCopilotConfig>;
|
|
6
|
+
}
|
|
7
|
+
export declare const FloatingCopilot: React.FC<FloatingCopilotProps>;
|
|
8
|
+
export default FloatingCopilot;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { defaultConfig } from './types';
|
|
3
|
+
import './FloatingCopilot.css';
|
|
4
|
+
export const FloatingCopilot = ({ config = {} }) => {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const finalConfig = Object.assign(Object.assign({}, defaultConfig), config);
|
|
7
|
+
const [messages, setMessages] = useState([]);
|
|
8
|
+
const [inputValue, setInputValue] = useState('');
|
|
9
|
+
const [isMinimized, setIsMinimized] = useState(finalConfig.minimized || false);
|
|
10
|
+
const [isMaximized, setIsMaximized] = useState(finalConfig.maximized || false);
|
|
11
|
+
const [position, setPosition] = useState({
|
|
12
|
+
x: ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) ? 20 : -999,
|
|
13
|
+
y: ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) ? 20 : -999,
|
|
14
|
+
});
|
|
15
|
+
const [size, setSize] = useState({
|
|
16
|
+
width: finalConfig.width || 350,
|
|
17
|
+
height: finalConfig.height || 500,
|
|
18
|
+
});
|
|
19
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
20
|
+
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
21
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
22
|
+
const containerRef = useRef(null);
|
|
23
|
+
const messagesEndRef = useRef(null);
|
|
24
|
+
// Auto-scroll to bottom when new messages arrive
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
var _a;
|
|
27
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
|
|
28
|
+
}, [messages]);
|
|
29
|
+
// Handle mouse down for dragging
|
|
30
|
+
const handleMouseDownDrag = (e) => {
|
|
31
|
+
if (!finalConfig.draggable || isMaximized)
|
|
32
|
+
return;
|
|
33
|
+
const container = containerRef.current;
|
|
34
|
+
if (!container)
|
|
35
|
+
return;
|
|
36
|
+
const rect = container.getBoundingClientRect();
|
|
37
|
+
setDragOffset({
|
|
38
|
+
x: e.clientX - rect.left,
|
|
39
|
+
y: e.clientY - rect.top,
|
|
40
|
+
});
|
|
41
|
+
setIsDragging(true);
|
|
42
|
+
};
|
|
43
|
+
// Handle mouse move for dragging
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const handleMouseMove = (e) => {
|
|
46
|
+
if (!isDragging || !containerRef.current)
|
|
47
|
+
return;
|
|
48
|
+
const newX = e.clientX - dragOffset.x;
|
|
49
|
+
const newY = e.clientY - dragOffset.y;
|
|
50
|
+
setPosition({
|
|
51
|
+
x: Math.max(0, Math.min(newX, window.innerWidth - size.width)),
|
|
52
|
+
y: Math.max(0, Math.min(newY, window.innerHeight - size.height)),
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const handleMouseUp = () => {
|
|
56
|
+
setIsDragging(false);
|
|
57
|
+
};
|
|
58
|
+
if (isDragging) {
|
|
59
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
60
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
61
|
+
}
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
64
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
65
|
+
};
|
|
66
|
+
}, [isDragging, dragOffset, size]);
|
|
67
|
+
// Handle resize
|
|
68
|
+
const handleMouseDownResize = (e) => {
|
|
69
|
+
if (!finalConfig.resizable || isMaximized)
|
|
70
|
+
return;
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setIsResizing(true);
|
|
73
|
+
};
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const handleMouseMove = (e) => {
|
|
76
|
+
if (!isResizing || !containerRef.current)
|
|
77
|
+
return;
|
|
78
|
+
const container = containerRef.current;
|
|
79
|
+
const rect = container.getBoundingClientRect();
|
|
80
|
+
const newWidth = Math.max(250, e.clientX - rect.left);
|
|
81
|
+
const newHeight = Math.max(300, e.clientY - rect.top);
|
|
82
|
+
setSize({
|
|
83
|
+
width: newWidth,
|
|
84
|
+
height: newHeight,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const handleMouseUp = () => {
|
|
88
|
+
setIsResizing(false);
|
|
89
|
+
};
|
|
90
|
+
if (isResizing) {
|
|
91
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
92
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
93
|
+
}
|
|
94
|
+
return () => {
|
|
95
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
96
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
97
|
+
};
|
|
98
|
+
}, [isResizing]);
|
|
99
|
+
// Handle message send
|
|
100
|
+
const handleSendMessage = () => {
|
|
101
|
+
var _a;
|
|
102
|
+
if (!inputValue.trim())
|
|
103
|
+
return;
|
|
104
|
+
const userMessage = {
|
|
105
|
+
id: Date.now().toString(),
|
|
106
|
+
text: inputValue,
|
|
107
|
+
sender: 'user',
|
|
108
|
+
timestamp: new Date(),
|
|
109
|
+
};
|
|
110
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
111
|
+
setInputValue('');
|
|
112
|
+
// Call the onMessage callback
|
|
113
|
+
(_a = finalConfig.onMessage) === null || _a === void 0 ? void 0 : _a.call(finalConfig, inputValue);
|
|
114
|
+
// Simulate bot response (replace with actual API call)
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
const botMessage = {
|
|
117
|
+
id: (Date.now() + 1).toString(),
|
|
118
|
+
text: 'Thanks for your message! This is a demo response.',
|
|
119
|
+
sender: 'bot',
|
|
120
|
+
timestamp: new Date(),
|
|
121
|
+
};
|
|
122
|
+
setMessages((prev) => [...prev, botMessage]);
|
|
123
|
+
}, 500);
|
|
124
|
+
};
|
|
125
|
+
// Handle minimize
|
|
126
|
+
const handleMinimize = () => {
|
|
127
|
+
var _a;
|
|
128
|
+
setIsMinimized(!isMinimized);
|
|
129
|
+
(_a = finalConfig.onMinimize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
130
|
+
};
|
|
131
|
+
// Handle maximize
|
|
132
|
+
const handleMaximize = () => {
|
|
133
|
+
var _a;
|
|
134
|
+
setIsMaximized(!isMaximized);
|
|
135
|
+
(_a = finalConfig.onMaximize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
136
|
+
};
|
|
137
|
+
// Handle close
|
|
138
|
+
const handleClose = () => {
|
|
139
|
+
var _a;
|
|
140
|
+
(_a = finalConfig.onClose) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
141
|
+
};
|
|
142
|
+
const getPositionStyle = () => {
|
|
143
|
+
var _a, _b;
|
|
144
|
+
if (isMaximized) {
|
|
145
|
+
return {
|
|
146
|
+
position: 'fixed',
|
|
147
|
+
top: 0,
|
|
148
|
+
left: 0,
|
|
149
|
+
right: 0,
|
|
150
|
+
bottom: 0,
|
|
151
|
+
width: '100%',
|
|
152
|
+
height: '100%',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const baseStyle = {
|
|
156
|
+
position: 'fixed',
|
|
157
|
+
zIndex: finalConfig.zIndex,
|
|
158
|
+
};
|
|
159
|
+
if ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) {
|
|
160
|
+
baseStyle.left = position.x;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
baseStyle.right = position.x;
|
|
164
|
+
}
|
|
165
|
+
if ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) {
|
|
166
|
+
baseStyle.top = position.y;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
baseStyle.bottom = position.y;
|
|
170
|
+
}
|
|
171
|
+
return baseStyle;
|
|
172
|
+
};
|
|
173
|
+
const getThemeStyle = () => {
|
|
174
|
+
var _a, _b, _c, _d;
|
|
175
|
+
return ({
|
|
176
|
+
'--primary-color': (_a = finalConfig.theme) === null || _a === void 0 ? void 0 : _a.primaryColor,
|
|
177
|
+
'--bg-color': (_b = finalConfig.theme) === null || _b === void 0 ? void 0 : _b.backgroundColor,
|
|
178
|
+
'--text-color': (_c = finalConfig.theme) === null || _c === void 0 ? void 0 : _c.textColor,
|
|
179
|
+
'--border-color': (_d = finalConfig.theme) === null || _d === void 0 ? void 0 : _d.borderColor,
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
return (React.createElement("div", { ref: containerRef, className: `floating-copilot ${isMaximized ? 'maximized' : ''} ${isMinimized ? 'minimized' : ''}`, style: Object.assign(Object.assign(Object.assign({}, getPositionStyle()), { width: isMaximized ? '100%' : size.width, height: isMaximized ? '100%' : size.height }), getThemeStyle()) },
|
|
183
|
+
finalConfig.showHeader && (React.createElement("div", { className: "copilot-header", onMouseDown: handleMouseDownDrag, style: { cursor: finalConfig.draggable ? 'grab' : 'default' } },
|
|
184
|
+
React.createElement("h3", { className: "copilot-title" }, finalConfig.headerContent || finalConfig.title),
|
|
185
|
+
React.createElement("div", { className: "copilot-buttons" },
|
|
186
|
+
finalConfig.showMinimizeBtn && (React.createElement("button", { className: "copilot-btn copilot-minimize-btn", onClick: handleMinimize, title: "Minimize" }, isMinimized ? '▲' : '▼')),
|
|
187
|
+
finalConfig.showMaximizeBtn && (React.createElement("button", { className: "copilot-btn copilot-maximize-btn", onClick: handleMaximize, title: "Maximize" }, isMaximized ? '▭' : '□')),
|
|
188
|
+
finalConfig.showCloseBtn && (React.createElement("button", { className: "copilot-btn copilot-close-btn", onClick: handleClose, title: "Close" }, "\u2715"))))),
|
|
189
|
+
!isMinimized && (React.createElement(React.Fragment, null,
|
|
190
|
+
React.createElement("div", { className: "copilot-messages" }, messages.length === 0 ? (React.createElement("div", { className: "copilot-empty-state" },
|
|
191
|
+
React.createElement("p", null, "Start a conversation..."))) : (React.createElement(React.Fragment, null,
|
|
192
|
+
messages.map((msg) => (React.createElement("div", { key: msg.id, className: `copilot-message copilot-message-${msg.sender}` },
|
|
193
|
+
React.createElement("div", { className: "copilot-message-content" }, msg.text),
|
|
194
|
+
React.createElement("span", { className: "copilot-message-time" }, msg.timestamp.toLocaleTimeString([], {
|
|
195
|
+
hour: '2-digit',
|
|
196
|
+
minute: '2-digit',
|
|
197
|
+
}))))),
|
|
198
|
+
React.createElement("div", { ref: messagesEndRef })))),
|
|
199
|
+
React.createElement("div", { className: "copilot-input-area" },
|
|
200
|
+
React.createElement("input", { type: "text", className: "copilot-input", placeholder: finalConfig.placeholder, value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyPress: (e) => {
|
|
201
|
+
if (e.key === 'Enter') {
|
|
202
|
+
handleSendMessage();
|
|
203
|
+
}
|
|
204
|
+
} }),
|
|
205
|
+
React.createElement("button", { className: "copilot-send-btn", onClick: handleSendMessage }, "Send")),
|
|
206
|
+
finalConfig.resizable && !isMaximized && (React.createElement("div", { className: "copilot-resize-handle", onMouseDown: handleMouseDownResize, title: "Drag to resize" }))))));
|
|
207
|
+
};
|
|
208
|
+
export default FloatingCopilot;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const defaultConfig = {
|
|
4
|
+
position: 'bottom-right',
|
|
5
|
+
width: 350,
|
|
6
|
+
height: 500,
|
|
7
|
+
draggable: true,
|
|
8
|
+
resizable: true,
|
|
9
|
+
minimized: false,
|
|
10
|
+
maximized: false,
|
|
11
|
+
zIndex: 9999,
|
|
12
|
+
showHeader: true,
|
|
13
|
+
showMinimizeBtn: true,
|
|
14
|
+
showMaximizeBtn: true,
|
|
15
|
+
showCloseBtn: true,
|
|
16
|
+
title: 'Chat Assistant',
|
|
17
|
+
placeholder: 'Type your message...',
|
|
18
|
+
theme: {
|
|
19
|
+
primaryColor: '#6366f1',
|
|
20
|
+
backgroundColor: '#ffffff',
|
|
21
|
+
textColor: '#1f2937',
|
|
22
|
+
borderColor: '#e5e7eb',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const FloatingCopilot = ({ config = {} }) => {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const finalConfig = Object.assign(Object.assign({}, defaultConfig), config);
|
|
29
|
+
const [messages, setMessages] = useState([]);
|
|
30
|
+
const [inputValue, setInputValue] = useState('');
|
|
31
|
+
const [isMinimized, setIsMinimized] = useState(finalConfig.minimized || false);
|
|
32
|
+
const [isMaximized, setIsMaximized] = useState(finalConfig.maximized || false);
|
|
33
|
+
const [position, setPosition] = useState({
|
|
34
|
+
x: ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) ? 20 : -999,
|
|
35
|
+
y: ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) ? 20 : -999,
|
|
36
|
+
});
|
|
37
|
+
const [size, setSize] = useState({
|
|
38
|
+
width: finalConfig.width || 350,
|
|
39
|
+
height: finalConfig.height || 500,
|
|
40
|
+
});
|
|
41
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
42
|
+
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
43
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
44
|
+
const containerRef = useRef(null);
|
|
45
|
+
const messagesEndRef = useRef(null);
|
|
46
|
+
// Auto-scroll to bottom when new messages arrive
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
var _a;
|
|
49
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
|
|
50
|
+
}, [messages]);
|
|
51
|
+
// Handle mouse down for dragging
|
|
52
|
+
const handleMouseDownDrag = (e) => {
|
|
53
|
+
if (!finalConfig.draggable || isMaximized)
|
|
54
|
+
return;
|
|
55
|
+
const container = containerRef.current;
|
|
56
|
+
if (!container)
|
|
57
|
+
return;
|
|
58
|
+
const rect = container.getBoundingClientRect();
|
|
59
|
+
setDragOffset({
|
|
60
|
+
x: e.clientX - rect.left,
|
|
61
|
+
y: e.clientY - rect.top,
|
|
62
|
+
});
|
|
63
|
+
setIsDragging(true);
|
|
64
|
+
};
|
|
65
|
+
// Handle mouse move for dragging
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const handleMouseMove = (e) => {
|
|
68
|
+
if (!isDragging || !containerRef.current)
|
|
69
|
+
return;
|
|
70
|
+
const newX = e.clientX - dragOffset.x;
|
|
71
|
+
const newY = e.clientY - dragOffset.y;
|
|
72
|
+
setPosition({
|
|
73
|
+
x: Math.max(0, Math.min(newX, window.innerWidth - size.width)),
|
|
74
|
+
y: Math.max(0, Math.min(newY, window.innerHeight - size.height)),
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const handleMouseUp = () => {
|
|
78
|
+
setIsDragging(false);
|
|
79
|
+
};
|
|
80
|
+
if (isDragging) {
|
|
81
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
82
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
83
|
+
}
|
|
84
|
+
return () => {
|
|
85
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
86
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
87
|
+
};
|
|
88
|
+
}, [isDragging, dragOffset, size]);
|
|
89
|
+
// Handle resize
|
|
90
|
+
const handleMouseDownResize = (e) => {
|
|
91
|
+
if (!finalConfig.resizable || isMaximized)
|
|
92
|
+
return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
setIsResizing(true);
|
|
95
|
+
};
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const handleMouseMove = (e) => {
|
|
98
|
+
if (!isResizing || !containerRef.current)
|
|
99
|
+
return;
|
|
100
|
+
const container = containerRef.current;
|
|
101
|
+
const rect = container.getBoundingClientRect();
|
|
102
|
+
const newWidth = Math.max(250, e.clientX - rect.left);
|
|
103
|
+
const newHeight = Math.max(300, e.clientY - rect.top);
|
|
104
|
+
setSize({
|
|
105
|
+
width: newWidth,
|
|
106
|
+
height: newHeight,
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
const handleMouseUp = () => {
|
|
110
|
+
setIsResizing(false);
|
|
111
|
+
};
|
|
112
|
+
if (isResizing) {
|
|
113
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
114
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
115
|
+
}
|
|
116
|
+
return () => {
|
|
117
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
118
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
119
|
+
};
|
|
120
|
+
}, [isResizing]);
|
|
121
|
+
// Handle message send
|
|
122
|
+
const handleSendMessage = () => {
|
|
123
|
+
var _a;
|
|
124
|
+
if (!inputValue.trim())
|
|
125
|
+
return;
|
|
126
|
+
const userMessage = {
|
|
127
|
+
id: Date.now().toString(),
|
|
128
|
+
text: inputValue,
|
|
129
|
+
sender: 'user',
|
|
130
|
+
timestamp: new Date(),
|
|
131
|
+
};
|
|
132
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
133
|
+
setInputValue('');
|
|
134
|
+
// Call the onMessage callback
|
|
135
|
+
(_a = finalConfig.onMessage) === null || _a === void 0 ? void 0 : _a.call(finalConfig, inputValue);
|
|
136
|
+
// Simulate bot response (replace with actual API call)
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
const botMessage = {
|
|
139
|
+
id: (Date.now() + 1).toString(),
|
|
140
|
+
text: 'Thanks for your message! This is a demo response.',
|
|
141
|
+
sender: 'bot',
|
|
142
|
+
timestamp: new Date(),
|
|
143
|
+
};
|
|
144
|
+
setMessages((prev) => [...prev, botMessage]);
|
|
145
|
+
}, 500);
|
|
146
|
+
};
|
|
147
|
+
// Handle minimize
|
|
148
|
+
const handleMinimize = () => {
|
|
149
|
+
var _a;
|
|
150
|
+
setIsMinimized(!isMinimized);
|
|
151
|
+
(_a = finalConfig.onMinimize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
152
|
+
};
|
|
153
|
+
// Handle maximize
|
|
154
|
+
const handleMaximize = () => {
|
|
155
|
+
var _a;
|
|
156
|
+
setIsMaximized(!isMaximized);
|
|
157
|
+
(_a = finalConfig.onMaximize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
158
|
+
};
|
|
159
|
+
// Handle close
|
|
160
|
+
const handleClose = () => {
|
|
161
|
+
var _a;
|
|
162
|
+
(_a = finalConfig.onClose) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
163
|
+
};
|
|
164
|
+
const getPositionStyle = () => {
|
|
165
|
+
var _a, _b;
|
|
166
|
+
if (isMaximized) {
|
|
167
|
+
return {
|
|
168
|
+
position: 'fixed',
|
|
169
|
+
top: 0,
|
|
170
|
+
left: 0,
|
|
171
|
+
right: 0,
|
|
172
|
+
bottom: 0,
|
|
173
|
+
width: '100%',
|
|
174
|
+
height: '100%',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const baseStyle = {
|
|
178
|
+
position: 'fixed',
|
|
179
|
+
zIndex: finalConfig.zIndex,
|
|
180
|
+
};
|
|
181
|
+
if ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) {
|
|
182
|
+
baseStyle.left = position.x;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
baseStyle.right = position.x;
|
|
186
|
+
}
|
|
187
|
+
if ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) {
|
|
188
|
+
baseStyle.top = position.y;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
baseStyle.bottom = position.y;
|
|
192
|
+
}
|
|
193
|
+
return baseStyle;
|
|
194
|
+
};
|
|
195
|
+
const getThemeStyle = () => {
|
|
196
|
+
var _a, _b, _c, _d;
|
|
197
|
+
return ({
|
|
198
|
+
'--primary-color': (_a = finalConfig.theme) === null || _a === void 0 ? void 0 : _a.primaryColor,
|
|
199
|
+
'--bg-color': (_b = finalConfig.theme) === null || _b === void 0 ? void 0 : _b.backgroundColor,
|
|
200
|
+
'--text-color': (_c = finalConfig.theme) === null || _c === void 0 ? void 0 : _c.textColor,
|
|
201
|
+
'--border-color': (_d = finalConfig.theme) === null || _d === void 0 ? void 0 : _d.borderColor,
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
return (React.createElement("div", { ref: containerRef, className: `floating-copilot ${isMaximized ? 'maximized' : ''} ${isMinimized ? 'minimized' : ''}`, style: Object.assign(Object.assign(Object.assign({}, getPositionStyle()), { width: isMaximized ? '100%' : size.width, height: isMaximized ? '100%' : size.height }), getThemeStyle()) },
|
|
205
|
+
finalConfig.showHeader && (React.createElement("div", { className: "copilot-header", onMouseDown: handleMouseDownDrag, style: { cursor: finalConfig.draggable ? 'grab' : 'default' } },
|
|
206
|
+
React.createElement("h3", { className: "copilot-title" }, finalConfig.headerContent || finalConfig.title),
|
|
207
|
+
React.createElement("div", { className: "copilot-buttons" },
|
|
208
|
+
finalConfig.showMinimizeBtn && (React.createElement("button", { className: "copilot-btn copilot-minimize-btn", onClick: handleMinimize, title: "Minimize" }, isMinimized ? '▲' : '▼')),
|
|
209
|
+
finalConfig.showMaximizeBtn && (React.createElement("button", { className: "copilot-btn copilot-maximize-btn", onClick: handleMaximize, title: "Maximize" }, isMaximized ? '▭' : '□')),
|
|
210
|
+
finalConfig.showCloseBtn && (React.createElement("button", { className: "copilot-btn copilot-close-btn", onClick: handleClose, title: "Close" }, "\u2715"))))),
|
|
211
|
+
!isMinimized && (React.createElement(React.Fragment, null,
|
|
212
|
+
React.createElement("div", { className: "copilot-messages" }, messages.length === 0 ? (React.createElement("div", { className: "copilot-empty-state" },
|
|
213
|
+
React.createElement("p", null, "Start a conversation..."))) : (React.createElement(React.Fragment, null,
|
|
214
|
+
messages.map((msg) => (React.createElement("div", { key: msg.id, className: `copilot-message copilot-message-${msg.sender}` },
|
|
215
|
+
React.createElement("div", { className: "copilot-message-content" }, msg.text),
|
|
216
|
+
React.createElement("span", { className: "copilot-message-time" }, msg.timestamp.toLocaleTimeString([], {
|
|
217
|
+
hour: '2-digit',
|
|
218
|
+
minute: '2-digit',
|
|
219
|
+
}))))),
|
|
220
|
+
React.createElement("div", { ref: messagesEndRef })))),
|
|
221
|
+
React.createElement("div", { className: "copilot-input-area" },
|
|
222
|
+
React.createElement("input", { type: "text", className: "copilot-input", placeholder: finalConfig.placeholder, value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyPress: (e) => {
|
|
223
|
+
if (e.key === 'Enter') {
|
|
224
|
+
handleSendMessage();
|
|
225
|
+
}
|
|
226
|
+
} }),
|
|
227
|
+
React.createElement("button", { className: "copilot-send-btn", onClick: handleSendMessage }, "Send")),
|
|
228
|
+
finalConfig.resizable && !isMaximized && (React.createElement("div", { className: "copilot-resize-handle", onMouseDown: handleMouseDownResize, title: "Drag to resize" }))))));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export { FloatingCopilot, FloatingCopilot as default, defaultConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var React = require('react');
|
|
6
|
+
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
position: 'bottom-right',
|
|
9
|
+
width: 350,
|
|
10
|
+
height: 500,
|
|
11
|
+
draggable: true,
|
|
12
|
+
resizable: true,
|
|
13
|
+
minimized: false,
|
|
14
|
+
maximized: false,
|
|
15
|
+
zIndex: 9999,
|
|
16
|
+
showHeader: true,
|
|
17
|
+
showMinimizeBtn: true,
|
|
18
|
+
showMaximizeBtn: true,
|
|
19
|
+
showCloseBtn: true,
|
|
20
|
+
title: 'Chat Assistant',
|
|
21
|
+
placeholder: 'Type your message...',
|
|
22
|
+
theme: {
|
|
23
|
+
primaryColor: '#6366f1',
|
|
24
|
+
backgroundColor: '#ffffff',
|
|
25
|
+
textColor: '#1f2937',
|
|
26
|
+
borderColor: '#e5e7eb',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const FloatingCopilot = ({ config = {} }) => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const finalConfig = Object.assign(Object.assign({}, defaultConfig), config);
|
|
33
|
+
const [messages, setMessages] = React.useState([]);
|
|
34
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
35
|
+
const [isMinimized, setIsMinimized] = React.useState(finalConfig.minimized || false);
|
|
36
|
+
const [isMaximized, setIsMaximized] = React.useState(finalConfig.maximized || false);
|
|
37
|
+
const [position, setPosition] = React.useState({
|
|
38
|
+
x: ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) ? 20 : -999,
|
|
39
|
+
y: ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) ? 20 : -999,
|
|
40
|
+
});
|
|
41
|
+
const [size, setSize] = React.useState({
|
|
42
|
+
width: finalConfig.width || 350,
|
|
43
|
+
height: finalConfig.height || 500,
|
|
44
|
+
});
|
|
45
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
46
|
+
const [dragOffset, setDragOffset] = React.useState({ x: 0, y: 0 });
|
|
47
|
+
const [isResizing, setIsResizing] = React.useState(false);
|
|
48
|
+
const containerRef = React.useRef(null);
|
|
49
|
+
const messagesEndRef = React.useRef(null);
|
|
50
|
+
// Auto-scroll to bottom when new messages arrive
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
var _a;
|
|
53
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' });
|
|
54
|
+
}, [messages]);
|
|
55
|
+
// Handle mouse down for dragging
|
|
56
|
+
const handleMouseDownDrag = (e) => {
|
|
57
|
+
if (!finalConfig.draggable || isMaximized)
|
|
58
|
+
return;
|
|
59
|
+
const container = containerRef.current;
|
|
60
|
+
if (!container)
|
|
61
|
+
return;
|
|
62
|
+
const rect = container.getBoundingClientRect();
|
|
63
|
+
setDragOffset({
|
|
64
|
+
x: e.clientX - rect.left,
|
|
65
|
+
y: e.clientY - rect.top,
|
|
66
|
+
});
|
|
67
|
+
setIsDragging(true);
|
|
68
|
+
};
|
|
69
|
+
// Handle mouse move for dragging
|
|
70
|
+
React.useEffect(() => {
|
|
71
|
+
const handleMouseMove = (e) => {
|
|
72
|
+
if (!isDragging || !containerRef.current)
|
|
73
|
+
return;
|
|
74
|
+
const newX = e.clientX - dragOffset.x;
|
|
75
|
+
const newY = e.clientY - dragOffset.y;
|
|
76
|
+
setPosition({
|
|
77
|
+
x: Math.max(0, Math.min(newX, window.innerWidth - size.width)),
|
|
78
|
+
y: Math.max(0, Math.min(newY, window.innerHeight - size.height)),
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
const handleMouseUp = () => {
|
|
82
|
+
setIsDragging(false);
|
|
83
|
+
};
|
|
84
|
+
if (isDragging) {
|
|
85
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
86
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
87
|
+
}
|
|
88
|
+
return () => {
|
|
89
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
90
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
91
|
+
};
|
|
92
|
+
}, [isDragging, dragOffset, size]);
|
|
93
|
+
// Handle resize
|
|
94
|
+
const handleMouseDownResize = (e) => {
|
|
95
|
+
if (!finalConfig.resizable || isMaximized)
|
|
96
|
+
return;
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
setIsResizing(true);
|
|
99
|
+
};
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
const handleMouseMove = (e) => {
|
|
102
|
+
if (!isResizing || !containerRef.current)
|
|
103
|
+
return;
|
|
104
|
+
const container = containerRef.current;
|
|
105
|
+
const rect = container.getBoundingClientRect();
|
|
106
|
+
const newWidth = Math.max(250, e.clientX - rect.left);
|
|
107
|
+
const newHeight = Math.max(300, e.clientY - rect.top);
|
|
108
|
+
setSize({
|
|
109
|
+
width: newWidth,
|
|
110
|
+
height: newHeight,
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const handleMouseUp = () => {
|
|
114
|
+
setIsResizing(false);
|
|
115
|
+
};
|
|
116
|
+
if (isResizing) {
|
|
117
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
118
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
119
|
+
}
|
|
120
|
+
return () => {
|
|
121
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
122
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
123
|
+
};
|
|
124
|
+
}, [isResizing]);
|
|
125
|
+
// Handle message send
|
|
126
|
+
const handleSendMessage = () => {
|
|
127
|
+
var _a;
|
|
128
|
+
if (!inputValue.trim())
|
|
129
|
+
return;
|
|
130
|
+
const userMessage = {
|
|
131
|
+
id: Date.now().toString(),
|
|
132
|
+
text: inputValue,
|
|
133
|
+
sender: 'user',
|
|
134
|
+
timestamp: new Date(),
|
|
135
|
+
};
|
|
136
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
137
|
+
setInputValue('');
|
|
138
|
+
// Call the onMessage callback
|
|
139
|
+
(_a = finalConfig.onMessage) === null || _a === void 0 ? void 0 : _a.call(finalConfig, inputValue);
|
|
140
|
+
// Simulate bot response (replace with actual API call)
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
const botMessage = {
|
|
143
|
+
id: (Date.now() + 1).toString(),
|
|
144
|
+
text: 'Thanks for your message! This is a demo response.',
|
|
145
|
+
sender: 'bot',
|
|
146
|
+
timestamp: new Date(),
|
|
147
|
+
};
|
|
148
|
+
setMessages((prev) => [...prev, botMessage]);
|
|
149
|
+
}, 500);
|
|
150
|
+
};
|
|
151
|
+
// Handle minimize
|
|
152
|
+
const handleMinimize = () => {
|
|
153
|
+
var _a;
|
|
154
|
+
setIsMinimized(!isMinimized);
|
|
155
|
+
(_a = finalConfig.onMinimize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
156
|
+
};
|
|
157
|
+
// Handle maximize
|
|
158
|
+
const handleMaximize = () => {
|
|
159
|
+
var _a;
|
|
160
|
+
setIsMaximized(!isMaximized);
|
|
161
|
+
(_a = finalConfig.onMaximize) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
162
|
+
};
|
|
163
|
+
// Handle close
|
|
164
|
+
const handleClose = () => {
|
|
165
|
+
var _a;
|
|
166
|
+
(_a = finalConfig.onClose) === null || _a === void 0 ? void 0 : _a.call(finalConfig);
|
|
167
|
+
};
|
|
168
|
+
const getPositionStyle = () => {
|
|
169
|
+
var _a, _b;
|
|
170
|
+
if (isMaximized) {
|
|
171
|
+
return {
|
|
172
|
+
position: 'fixed',
|
|
173
|
+
top: 0,
|
|
174
|
+
left: 0,
|
|
175
|
+
right: 0,
|
|
176
|
+
bottom: 0,
|
|
177
|
+
width: '100%',
|
|
178
|
+
height: '100%',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const baseStyle = {
|
|
182
|
+
position: 'fixed',
|
|
183
|
+
zIndex: finalConfig.zIndex,
|
|
184
|
+
};
|
|
185
|
+
if ((_a = finalConfig.position) === null || _a === void 0 ? void 0 : _a.includes('left')) {
|
|
186
|
+
baseStyle.left = position.x;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
baseStyle.right = position.x;
|
|
190
|
+
}
|
|
191
|
+
if ((_b = finalConfig.position) === null || _b === void 0 ? void 0 : _b.includes('top')) {
|
|
192
|
+
baseStyle.top = position.y;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
baseStyle.bottom = position.y;
|
|
196
|
+
}
|
|
197
|
+
return baseStyle;
|
|
198
|
+
};
|
|
199
|
+
const getThemeStyle = () => {
|
|
200
|
+
var _a, _b, _c, _d;
|
|
201
|
+
return ({
|
|
202
|
+
'--primary-color': (_a = finalConfig.theme) === null || _a === void 0 ? void 0 : _a.primaryColor,
|
|
203
|
+
'--bg-color': (_b = finalConfig.theme) === null || _b === void 0 ? void 0 : _b.backgroundColor,
|
|
204
|
+
'--text-color': (_c = finalConfig.theme) === null || _c === void 0 ? void 0 : _c.textColor,
|
|
205
|
+
'--border-color': (_d = finalConfig.theme) === null || _d === void 0 ? void 0 : _d.borderColor,
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
return (React.createElement("div", { ref: containerRef, className: `floating-copilot ${isMaximized ? 'maximized' : ''} ${isMinimized ? 'minimized' : ''}`, style: Object.assign(Object.assign(Object.assign({}, getPositionStyle()), { width: isMaximized ? '100%' : size.width, height: isMaximized ? '100%' : size.height }), getThemeStyle()) },
|
|
209
|
+
finalConfig.showHeader && (React.createElement("div", { className: "copilot-header", onMouseDown: handleMouseDownDrag, style: { cursor: finalConfig.draggable ? 'grab' : 'default' } },
|
|
210
|
+
React.createElement("h3", { className: "copilot-title" }, finalConfig.headerContent || finalConfig.title),
|
|
211
|
+
React.createElement("div", { className: "copilot-buttons" },
|
|
212
|
+
finalConfig.showMinimizeBtn && (React.createElement("button", { className: "copilot-btn copilot-minimize-btn", onClick: handleMinimize, title: "Minimize" }, isMinimized ? '▲' : '▼')),
|
|
213
|
+
finalConfig.showMaximizeBtn && (React.createElement("button", { className: "copilot-btn copilot-maximize-btn", onClick: handleMaximize, title: "Maximize" }, isMaximized ? '▭' : '□')),
|
|
214
|
+
finalConfig.showCloseBtn && (React.createElement("button", { className: "copilot-btn copilot-close-btn", onClick: handleClose, title: "Close" }, "\u2715"))))),
|
|
215
|
+
!isMinimized && (React.createElement(React.Fragment, null,
|
|
216
|
+
React.createElement("div", { className: "copilot-messages" }, messages.length === 0 ? (React.createElement("div", { className: "copilot-empty-state" },
|
|
217
|
+
React.createElement("p", null, "Start a conversation..."))) : (React.createElement(React.Fragment, null,
|
|
218
|
+
messages.map((msg) => (React.createElement("div", { key: msg.id, className: `copilot-message copilot-message-${msg.sender}` },
|
|
219
|
+
React.createElement("div", { className: "copilot-message-content" }, msg.text),
|
|
220
|
+
React.createElement("span", { className: "copilot-message-time" }, msg.timestamp.toLocaleTimeString([], {
|
|
221
|
+
hour: '2-digit',
|
|
222
|
+
minute: '2-digit',
|
|
223
|
+
}))))),
|
|
224
|
+
React.createElement("div", { ref: messagesEndRef })))),
|
|
225
|
+
React.createElement("div", { className: "copilot-input-area" },
|
|
226
|
+
React.createElement("input", { type: "text", className: "copilot-input", placeholder: finalConfig.placeholder, value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyPress: (e) => {
|
|
227
|
+
if (e.key === 'Enter') {
|
|
228
|
+
handleSendMessage();
|
|
229
|
+
}
|
|
230
|
+
} }),
|
|
231
|
+
React.createElement("button", { className: "copilot-send-btn", onClick: handleSendMessage }, "Send")),
|
|
232
|
+
finalConfig.resizable && !isMaximized && (React.createElement("div", { className: "copilot-resize-handle", onMouseDown: handleMouseDownResize, title: "Drag to resize" }))))));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
exports.FloatingCopilot = FloatingCopilot;
|
|
236
|
+
exports.default = FloatingCopilot;
|
|
237
|
+
exports.defaultConfig = defaultConfig;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface FloatingCopilotConfig {
|
|
2
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
3
|
+
width?: number;
|
|
4
|
+
height?: number;
|
|
5
|
+
draggable?: boolean;
|
|
6
|
+
resizable?: boolean;
|
|
7
|
+
theme?: {
|
|
8
|
+
primaryColor?: string;
|
|
9
|
+
backgroundColor?: string;
|
|
10
|
+
textColor?: string;
|
|
11
|
+
borderColor?: string;
|
|
12
|
+
};
|
|
13
|
+
title?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
onMessage?: (message: string) => void;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
onMinimize?: () => void;
|
|
18
|
+
onMaximize?: () => void;
|
|
19
|
+
minimized?: boolean;
|
|
20
|
+
maximized?: boolean;
|
|
21
|
+
zIndex?: number;
|
|
22
|
+
showHeader?: boolean;
|
|
23
|
+
showMinimizeBtn?: boolean;
|
|
24
|
+
showMaximizeBtn?: boolean;
|
|
25
|
+
showCloseBtn?: boolean;
|
|
26
|
+
headerContent?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const defaultConfig: FloatingCopilotConfig;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const defaultConfig = {
|
|
2
|
+
position: 'bottom-right',
|
|
3
|
+
width: 350,
|
|
4
|
+
height: 500,
|
|
5
|
+
draggable: true,
|
|
6
|
+
resizable: true,
|
|
7
|
+
minimized: false,
|
|
8
|
+
maximized: false,
|
|
9
|
+
zIndex: 9999,
|
|
10
|
+
showHeader: true,
|
|
11
|
+
showMinimizeBtn: true,
|
|
12
|
+
showMaximizeBtn: true,
|
|
13
|
+
showCloseBtn: true,
|
|
14
|
+
title: 'Chat Assistant',
|
|
15
|
+
placeholder: 'Type your message...',
|
|
16
|
+
theme: {
|
|
17
|
+
primaryColor: '#6366f1',
|
|
18
|
+
backgroundColor: '#ffffff',
|
|
19
|
+
textColor: '#1f2937',
|
|
20
|
+
borderColor: '#e5e7eb',
|
|
21
|
+
},
|
|
22
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "floating-copilot-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A highly configurable floating chat widget plugin for React websites. Draggable, resizable, themeable, and production-ready.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/yourusername/floating-copilot-widget.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/yourusername/floating-copilot-widget",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/yourusername/floating-copilot-widget/issues"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && rollup -c && node scripts/copy-css.js",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"test": "jest",
|
|
25
|
+
"demo": "react-scripts start"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"floating",
|
|
30
|
+
"chat",
|
|
31
|
+
"widget",
|
|
32
|
+
"plugin",
|
|
33
|
+
"modal",
|
|
34
|
+
"draggable",
|
|
35
|
+
"resizable",
|
|
36
|
+
"minimize",
|
|
37
|
+
"maximize",
|
|
38
|
+
"configurable"
|
|
39
|
+
],
|
|
40
|
+
"author": "Your Name <your.email@example.com>",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=14.0.0",
|
|
44
|
+
"npm": ">=6.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
48
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "^18.0.0",
|
|
52
|
+
"@types/react-dom": "^18.0.0",
|
|
53
|
+
"react": "^18.2.0",
|
|
54
|
+
"react-dom": "^18.2.0",
|
|
55
|
+
"typescript": "^4.9.5",
|
|
56
|
+
"rollup": "^3.0.0",
|
|
57
|
+
"rollup-plugin-node-resolve": "^5.0.0",
|
|
58
|
+
"rollup-plugin-commonjs": "^10.0.0",
|
|
59
|
+
"rollup-plugin-typescript2": "^0.34.0",
|
|
60
|
+
"react-scripts": "5.0.1"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {}
|
|
63
|
+
}
|