acecoderz-chat-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/browser/index.ts +15 -0
- package/dist/adapters/react/ChatUI.d.ts +32 -0
- package/dist/adapters/react/ChatUI.d.ts.map +1 -0
- package/dist/adapters/react/ChatUI.js +170 -0
- package/dist/adapters/react/index.d.ts +5 -0
- package/dist/adapters/react/index.d.ts.map +1 -0
- package/dist/adapters/react/index.js +2 -0
- package/dist/adapters/react/useChat.d.ts +14 -0
- package/dist/adapters/react/useChat.d.ts.map +1 -0
- package/dist/adapters/react/useChat.js +64 -0
- package/dist/adapters/solid/createChat.d.ts +13 -0
- package/dist/adapters/solid/createChat.d.ts.map +1 -0
- package/dist/adapters/solid/createChat.js +34 -0
- package/dist/adapters/solid/index.d.ts +3 -0
- package/dist/adapters/solid/index.d.ts.map +1 -0
- package/dist/adapters/solid/index.js +1 -0
- package/dist/adapters/vanilla/index.d.ts +31 -0
- package/dist/adapters/vanilla/index.d.ts.map +1 -0
- package/dist/adapters/vanilla/index.js +346 -0
- package/dist/browser/Archive.zip +0 -0
- package/dist/browser/cdn-example.html +177 -0
- package/dist/browser/chatbot-ui.css +508 -0
- package/dist/browser/chatbot-ui.js +878 -0
- package/dist/browser/chatbot-ui.js.map +7 -0
- package/dist/browser/chatbot-ui.min.js +71 -0
- package/dist/browser/chatbot.html +100 -0
- package/dist/core/src/ChatEngine.d.ts +30 -0
- package/dist/core/src/ChatEngine.d.ts.map +1 -0
- package/dist/core/src/ChatEngine.js +357 -0
- package/dist/core/src/apiLogger.d.ts +47 -0
- package/dist/core/src/apiLogger.d.ts.map +1 -0
- package/dist/core/src/apiLogger.js +199 -0
- package/dist/core/src/index.d.ts +7 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +3 -0
- package/dist/core/src/types.d.ts +62 -0
- package/dist/core/src/types.d.ts.map +1 -0
- package/dist/core/src/types.js +1 -0
- package/dist/core/src/urlWhitelist.d.ts +19 -0
- package/dist/core/src/urlWhitelist.d.ts.map +1 -0
- package/dist/core/src/urlWhitelist.js +66 -0
- package/dist/src/ChatUI.stories.d.ts +37 -0
- package/dist/src/ChatUI.stories.d.ts.map +1 -0
- package/dist/src/ChatUI.stories.js +65 -0
- package/dist/src/ChatUIThemes.stories.d.ts +28 -0
- package/dist/src/ChatUIThemes.stories.d.ts.map +1 -0
- package/dist/src/ChatUIThemes.stories.js +109 -0
- package/dist/src/ThemeProperties.stories.d.ts +92 -0
- package/dist/src/ThemeProperties.stories.d.ts.map +1 -0
- package/dist/src/ThemeProperties.stories.js +195 -0
- package/dist/src/UseChat.stories.d.ts +21 -0
- package/dist/src/UseChat.stories.d.ts.map +1 -0
- package/dist/src/UseChat.stories.js +66 -0
- package/dist/src/VanillaAdapter.stories.d.ts +39 -0
- package/dist/src/VanillaAdapter.stories.d.ts.map +1 -0
- package/dist/src/VanillaAdapter.stories.js +78 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +8 -0
- package/package.json +117 -0
- package/styles/chat.css +508 -0
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# @chatbot/chat-ui
|
|
2
|
+
|
|
3
|
+
A framework-agnostic, fully customizable chat UI package that works with React, Solid, Vanilla JS, and any other framework.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Fully Customizable** - Theme via CSS variables or props
|
|
8
|
+
- 🔌 **Framework Agnostic** - Works with React, Solid, Vue, Svelte, and Vanilla JS
|
|
9
|
+
- 🚀 **Lightweight Core** - Zero framework dependencies in the core
|
|
10
|
+
- 💬 **Real-time Support** - WebSocket and HTTP API support
|
|
11
|
+
- 🎯 **TypeScript** - Full TypeScript support
|
|
12
|
+
- 📦 **Modular** - Import only what you need
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### npm/pnpm/yarn
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @chatbot/chat-ui
|
|
20
|
+
# or
|
|
21
|
+
npm install @chatbot/chat-ui
|
|
22
|
+
# or
|
|
23
|
+
yarn add @chatbot/chat-ui
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Browser / CDN (Script Tag)
|
|
27
|
+
|
|
28
|
+
For browser usage without a build step, see [CDN.md](./CDN.md) for detailed documentation.
|
|
29
|
+
|
|
30
|
+
**Quick Start:**
|
|
31
|
+
```html
|
|
32
|
+
<!-- Include CSS -->
|
|
33
|
+
<link rel="stylesheet" href="https://cdn.example.com/chatbot-ui.css">
|
|
34
|
+
|
|
35
|
+
<!-- Include JavaScript -->
|
|
36
|
+
<script src="https://cdn.example.com/chatbot-ui.min.js"></script>
|
|
37
|
+
|
|
38
|
+
<!-- Add container -->
|
|
39
|
+
<div id="chatbot-container"></div>
|
|
40
|
+
|
|
41
|
+
<!-- Initialize -->
|
|
42
|
+
<script>
|
|
43
|
+
ChatbotUI.init('chatbot-container', {
|
|
44
|
+
config: {
|
|
45
|
+
apiUrl: 'https://your-api.com/api/chat',
|
|
46
|
+
},
|
|
47
|
+
theme: {
|
|
48
|
+
primaryColor: '#3b82f6',
|
|
49
|
+
userMessageColor: '#3b82f6',
|
|
50
|
+
assistantMessageColor: '#f1f5f9',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Building for CDN:**
|
|
57
|
+
```bash
|
|
58
|
+
# Build browser bundle
|
|
59
|
+
pnpm build:browser
|
|
60
|
+
|
|
61
|
+
# Output files will be in dist/browser/:
|
|
62
|
+
# - chatbot-ui.min.js (production)
|
|
63
|
+
# - chatbot-ui.js (development)
|
|
64
|
+
# - chatbot-ui.css (styles)
|
|
65
|
+
# - cdn-example.html (example file)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Iframe Embedding
|
|
69
|
+
|
|
70
|
+
After building, you can embed the chatbot in an iframe:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<iframe
|
|
74
|
+
src="https://cdn.example.com/chatbot.html?apiUrl=https://your-api.com/api/chat"
|
|
75
|
+
width="400"
|
|
76
|
+
height="600"
|
|
77
|
+
frameborder="0">
|
|
78
|
+
</iframe>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## CSS Setup
|
|
82
|
+
|
|
83
|
+
Import the CSS file in your project:
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
@import '@chatbot/chat-ui/styles';
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or in your JavaScript/TypeScript:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import '@chatbot/chat-ui/styles';
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
### React
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { ChatUI } from '@chatbot/chat-ui/react';
|
|
101
|
+
import '@chatbot/chat-ui/styles';
|
|
102
|
+
|
|
103
|
+
function App() {
|
|
104
|
+
return (
|
|
105
|
+
<ChatUI
|
|
106
|
+
config={{
|
|
107
|
+
apiUrl: 'http://localhost:3000/api',
|
|
108
|
+
}}
|
|
109
|
+
theme={{
|
|
110
|
+
primaryColor: '#3b82f6',
|
|
111
|
+
userMessageColor: '#3b82f6',
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or use the hook directly:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { useChat } from '@chatbot/chat-ui/react';
|
|
122
|
+
|
|
123
|
+
function CustomChat() {
|
|
124
|
+
const { messages, input, sendMessage, setInput } = useChat({
|
|
125
|
+
apiUrl: 'http://localhost:3000/api',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div>
|
|
130
|
+
{messages.map(msg => (
|
|
131
|
+
<div key={msg.id}>{msg.content}</div>
|
|
132
|
+
))}
|
|
133
|
+
<input value={input} onChange={e => setInput(e.target.value)} />
|
|
134
|
+
<button onClick={() => sendMessage(input)}>Send</button>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Solid
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { createChat } from '@chatbot/chat-ui/solid';
|
|
144
|
+
import '@chatbot/chat-ui/styles';
|
|
145
|
+
|
|
146
|
+
function App() {
|
|
147
|
+
const chat = createChat({
|
|
148
|
+
apiUrl: 'http://localhost:3000/api',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div>
|
|
153
|
+
<For each={chat.messages()}>
|
|
154
|
+
{(msg) => <div>{msg.content}</div>}
|
|
155
|
+
</For>
|
|
156
|
+
<input
|
|
157
|
+
value={chat.input()}
|
|
158
|
+
onInput={e => chat.setInput(e.target.value)}
|
|
159
|
+
/>
|
|
160
|
+
<button onClick={() => chat.sendMessage(chat.input())}>
|
|
161
|
+
Send
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Vanilla JS
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
import { createChatUI } from '@chatbot/chat-ui/vanilla';
|
|
172
|
+
import '@chatbot/chat-ui/styles';
|
|
173
|
+
|
|
174
|
+
const container = document.getElementById('chat-container');
|
|
175
|
+
|
|
176
|
+
const chatUI = createChatUI({
|
|
177
|
+
config: {
|
|
178
|
+
apiUrl: 'http://localhost:3000/api',
|
|
179
|
+
},
|
|
180
|
+
theme: {
|
|
181
|
+
primaryColor: '#3b82f6',
|
|
182
|
+
},
|
|
183
|
+
container,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Later, to destroy:
|
|
187
|
+
// chatUI.destroy();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Core Only (Advanced)
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { ChatEngine } from '@chatbot/chat-ui/core';
|
|
194
|
+
|
|
195
|
+
const engine = new ChatEngine({
|
|
196
|
+
apiUrl: 'http://localhost:3000/api',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
engine.on('messagesChange', (messages) => {
|
|
200
|
+
console.log('Messages updated:', messages);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
engine.sendMessage('Hello!');
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Theming
|
|
207
|
+
|
|
208
|
+
### CSS Variables
|
|
209
|
+
|
|
210
|
+
The package uses CSS variables for theming. You can override them:
|
|
211
|
+
|
|
212
|
+
```css
|
|
213
|
+
.chat-container {
|
|
214
|
+
--chat-primary-color: #your-color;
|
|
215
|
+
--chat-user-message-color: #your-color;
|
|
216
|
+
--chat-assistant-message-color: #your-color;
|
|
217
|
+
--chat-border-radius: 0.5rem;
|
|
218
|
+
--chat-font-family: 'Your Font', sans-serif;
|
|
219
|
+
/* ... and more */
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Theme Props (React)
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<ChatUI
|
|
227
|
+
config={config}
|
|
228
|
+
theme={{
|
|
229
|
+
primaryColor: '#3b82f6',
|
|
230
|
+
userMessageColor: '#3b82f6',
|
|
231
|
+
assistantMessageColor: '#f1f5f9',
|
|
232
|
+
borderRadius: '0.5rem',
|
|
233
|
+
fontFamily: 'system-ui, sans-serif',
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Configuration
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface ChatConfig {
|
|
242
|
+
apiUrl?: string; // Backend API URL
|
|
243
|
+
apiKey?: string; // API key for authentication
|
|
244
|
+
headers?: Record<string, string>; // Custom headers
|
|
245
|
+
onMessage?: (message: Message) => void; // Message callback
|
|
246
|
+
onError?: (error: Error) => void; // Error callback
|
|
247
|
+
enableWebSocket?: boolean; // Enable WebSocket
|
|
248
|
+
websocketUrl?: string; // WebSocket URL
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Customization
|
|
253
|
+
|
|
254
|
+
### Custom Message Renderer (React)
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<ChatUI
|
|
258
|
+
config={config}
|
|
259
|
+
customMessageRenderer={(message) => (
|
|
260
|
+
<div className="my-custom-message">
|
|
261
|
+
{message.content}
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Custom Input Renderer (React)
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<ChatUI
|
|
271
|
+
config={config}
|
|
272
|
+
customInputRenderer={({ value, onChange, onSend, disabled }) => (
|
|
273
|
+
<div>
|
|
274
|
+
<textarea value={value} onChange={e => onChange(e.target.value)} />
|
|
275
|
+
<button onClick={onSend} disabled={disabled}>Send</button>
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
/>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Backend API
|
|
282
|
+
|
|
283
|
+
The package expects a backend API with the following endpoint:
|
|
284
|
+
|
|
285
|
+
### POST `/api/chat`
|
|
286
|
+
|
|
287
|
+
Request:
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"message": "Hello!",
|
|
291
|
+
"conversationId": "optional-conversation-id"
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Response:
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"id": "message-id",
|
|
299
|
+
"message": "Response message",
|
|
300
|
+
"content": "Response message",
|
|
301
|
+
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
302
|
+
"metadata": {}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
MIT
|
|
309
|
+
|
package/browser/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Entry Point
|
|
3
|
+
* This file is used to create the browser bundle (UMD format)
|
|
4
|
+
* It imports the vanilla adapter, exposing ChatbotUI globally
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Import the vanilla adapter (which already sets up window.ChatbotUI)
|
|
8
|
+
import { createChatUI } from '../adapters/vanilla/index.js';
|
|
9
|
+
|
|
10
|
+
// Re-export for convenience
|
|
11
|
+
export { createChatUI };
|
|
12
|
+
export type { ChatUIOptions, ChatUIInstance } from '../adapters/vanilla/index.js';
|
|
13
|
+
|
|
14
|
+
// The vanilla adapter already sets up window.ChatbotUI when in browser context
|
|
15
|
+
// This ensures it's available globally when loaded via script tag
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChatConfig, ChatTheme, Message } from '../../core/src/types';
|
|
3
|
+
export interface ChatUIProps {
|
|
4
|
+
config: ChatConfig;
|
|
5
|
+
theme?: ChatTheme;
|
|
6
|
+
className?: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
showTimestamp?: boolean;
|
|
9
|
+
showAvatar?: boolean;
|
|
10
|
+
userAvatar?: string | React.ReactNode;
|
|
11
|
+
assistantAvatar?: string | React.ReactNode;
|
|
12
|
+
maxHeight?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
initialGreeting?: string;
|
|
15
|
+
messagesContainerClassName?: string;
|
|
16
|
+
messageClassName?: string;
|
|
17
|
+
inputContainerClassName?: string;
|
|
18
|
+
inputClassName?: string;
|
|
19
|
+
buttonClassName?: string;
|
|
20
|
+
errorClassName?: string;
|
|
21
|
+
emptyStateClassName?: string;
|
|
22
|
+
customMessageRenderer?: (message: Message) => React.ReactNode;
|
|
23
|
+
customInputRenderer?: (props: {
|
|
24
|
+
value: string;
|
|
25
|
+
onChange: (value: string) => void;
|
|
26
|
+
onSend: () => void;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
}) => React.ReactNode;
|
|
29
|
+
dataAttributes?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export declare const ChatUI: React.FC<ChatUIProps>;
|
|
32
|
+
//# sourceMappingURL=ChatUI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatUI.d.ts","sourceRoot":"","sources":["../../../adapters/react/ChatUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAuD3E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE;QAC5B,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,MAAM,EAAE,MAAM,IAAI,CAAC;QACnB,QAAQ,EAAE,OAAO,CAAC;KACnB,KAAK,KAAK,CAAC,SAAS,CAAC;IAEtB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAgOxC,CAAC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useChat } from './useChat';
|
|
4
|
+
// Optional markdown support - will be dynamically imported if available
|
|
5
|
+
// Add react-markdown and remark-gfm to your app's dependencies to enable markdown rendering
|
|
6
|
+
const MarkdownContent = ({ content }) => {
|
|
7
|
+
const [MarkdownRenderer, setMarkdownRenderer] = React.useState(null);
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
// @ts-ignore - react-markdown is an optional dependency
|
|
10
|
+
import('react-markdown')
|
|
11
|
+
.then((module) => {
|
|
12
|
+
// @ts-ignore - remark-gfm is an optional dependency
|
|
13
|
+
import('remark-gfm')
|
|
14
|
+
.then((gfm) => {
|
|
15
|
+
setMarkdownRenderer(() => {
|
|
16
|
+
const ReactMarkdown = module.default;
|
|
17
|
+
const remarkGfm = gfm.default;
|
|
18
|
+
// Note: react-markdown v10+ removed className prop
|
|
19
|
+
// We wrap it in a div with the className instead
|
|
20
|
+
return (props) => (_jsx("div", { className: "chat-markdown", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: props.children }) }));
|
|
21
|
+
});
|
|
22
|
+
})
|
|
23
|
+
.catch(() => {
|
|
24
|
+
// remark-gfm not available, use basic markdown
|
|
25
|
+
setMarkdownRenderer(() => {
|
|
26
|
+
const ReactMarkdown = module.default;
|
|
27
|
+
return (props) => (_jsx("div", { className: "chat-markdown", children: _jsx(ReactMarkdown, { children: props.children }) }));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
.catch(() => {
|
|
32
|
+
// react-markdown not available, will use plain text
|
|
33
|
+
});
|
|
34
|
+
}, []);
|
|
35
|
+
if (MarkdownRenderer) {
|
|
36
|
+
return _jsx(MarkdownRenderer, { children: content });
|
|
37
|
+
}
|
|
38
|
+
return _jsx("div", { className: "chat-markdown", children: content });
|
|
39
|
+
};
|
|
40
|
+
export const ChatUI = ({ config, theme = {}, className = '', placeholder = 'Type your message...', showTimestamp = true, showAvatar = true, userAvatar, assistantAvatar, maxHeight = '600px', disabled = false, initialGreeting, messagesContainerClassName = '', messageClassName = '', inputContainerClassName = '', inputClassName = '', buttonClassName = '', errorClassName = '', emptyStateClassName = '', customMessageRenderer, customInputRenderer, dataAttributes = {}, }) => {
|
|
41
|
+
const { messages, input, isLoading, error, sendMessage, setInput, addMessage, } = useChat(config);
|
|
42
|
+
// Add initial greeting message when component mounts
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (initialGreeting && messages.length === 0) {
|
|
45
|
+
const greetingMessage = {
|
|
46
|
+
id: `greeting-${Date.now()}`,
|
|
47
|
+
content: initialGreeting,
|
|
48
|
+
role: 'assistant',
|
|
49
|
+
timestamp: new Date(),
|
|
50
|
+
};
|
|
51
|
+
addMessage(greetingMessage);
|
|
52
|
+
}
|
|
53
|
+
}, [initialGreeting, messages.length, addMessage]);
|
|
54
|
+
const handleSubmit = (e) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
if (input.trim() && !isLoading && !disabled) {
|
|
57
|
+
sendMessage(input);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const formatTimestamp = (date) => {
|
|
61
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
62
|
+
hour: '2-digit',
|
|
63
|
+
minute: '2-digit',
|
|
64
|
+
}).format(date);
|
|
65
|
+
};
|
|
66
|
+
const renderAvatar = (role) => {
|
|
67
|
+
if (role === 'system')
|
|
68
|
+
return null;
|
|
69
|
+
if (!showAvatar)
|
|
70
|
+
return null;
|
|
71
|
+
const avatar = role === 'user' ? userAvatar : assistantAvatar;
|
|
72
|
+
if (typeof avatar === 'string') {
|
|
73
|
+
return (_jsx("div", { className: "chat-avatar", children: _jsx("img", { src: avatar, alt: role }) }));
|
|
74
|
+
}
|
|
75
|
+
if (React.isValidElement(avatar)) {
|
|
76
|
+
return _jsx("div", { className: "chat-avatar", children: avatar });
|
|
77
|
+
}
|
|
78
|
+
return (_jsx("div", { className: "chat-avatar", children: role === 'user' ? 'U' : 'A' }));
|
|
79
|
+
};
|
|
80
|
+
const renderMessage = (message) => {
|
|
81
|
+
if (customMessageRenderer) {
|
|
82
|
+
return customMessageRenderer(message);
|
|
83
|
+
}
|
|
84
|
+
const isUser = message.role === 'user';
|
|
85
|
+
return (_jsxs("div", { className: `chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'} ${messageClassName}`, "data-message-role": message.role, "data-message-id": message.id, children: [!isUser && renderAvatar(message.role), _jsxs("div", { className: `chat-message-content ${isUser ? 'chat-message-content-user' : 'chat-message-content-assistant'}`, children: [_jsx("div", { className: "chat-message-text", children: isUser ? (message.content) : (_jsx(MarkdownContent, { content: message.content })) }), showTimestamp && (_jsx("div", { className: "chat-message-timestamp", children: formatTimestamp(message.timestamp) }))] }), isUser && renderAvatar(message.role)] }, message.id));
|
|
86
|
+
};
|
|
87
|
+
// Apply theme via CSS variables (only set if provided)
|
|
88
|
+
const themeStyles = {};
|
|
89
|
+
if (theme?.primaryColor)
|
|
90
|
+
themeStyles['--chat-primary-color'] = theme.primaryColor;
|
|
91
|
+
if (theme?.secondaryColor)
|
|
92
|
+
themeStyles['--chat-secondary-color'] = theme.secondaryColor;
|
|
93
|
+
if (theme?.backgroundColor)
|
|
94
|
+
themeStyles['--chat-background-color'] = theme.backgroundColor;
|
|
95
|
+
if (theme?.textColor)
|
|
96
|
+
themeStyles['--chat-text-color'] = theme.textColor;
|
|
97
|
+
if (theme?.userMessageColor)
|
|
98
|
+
themeStyles['--chat-user-message-color'] = theme.userMessageColor;
|
|
99
|
+
if (theme?.assistantMessageColor)
|
|
100
|
+
themeStyles['--chat-assistant-message-color'] = theme.assistantMessageColor;
|
|
101
|
+
if (theme?.inputBackgroundColor)
|
|
102
|
+
themeStyles['--chat-input-background-color'] = theme.inputBackgroundColor;
|
|
103
|
+
if (theme?.inputTextColor)
|
|
104
|
+
themeStyles['--chat-input-text-color'] = theme.inputTextColor;
|
|
105
|
+
if (theme?.errorBackgroundColor)
|
|
106
|
+
themeStyles['--chat-error-background-color'] = theme.errorBackgroundColor;
|
|
107
|
+
if (theme?.errorTextColor)
|
|
108
|
+
themeStyles['--chat-error-text-color'] = theme.errorTextColor;
|
|
109
|
+
if (theme?.borderColor)
|
|
110
|
+
themeStyles['--chat-border-color'] = theme.borderColor;
|
|
111
|
+
if (theme?.fontFamily)
|
|
112
|
+
themeStyles['--chat-font-family'] = theme.fontFamily;
|
|
113
|
+
if (theme?.fontSize)
|
|
114
|
+
themeStyles['--chat-font-size'] = theme.fontSize;
|
|
115
|
+
if (theme?.fontWeight)
|
|
116
|
+
themeStyles['--chat-font-weight'] = theme.fontWeight;
|
|
117
|
+
if (theme?.lineHeight)
|
|
118
|
+
themeStyles['--chat-line-height'] = theme.lineHeight;
|
|
119
|
+
if (theme?.padding)
|
|
120
|
+
themeStyles['--chat-padding'] = theme.padding;
|
|
121
|
+
if (theme?.messageGap)
|
|
122
|
+
themeStyles['--chat-message-gap'] = theme.messageGap;
|
|
123
|
+
if (theme?.inputPadding)
|
|
124
|
+
themeStyles['--chat-input-padding'] = theme.inputPadding;
|
|
125
|
+
if (theme?.messagePadding)
|
|
126
|
+
themeStyles['--chat-message-padding'] = theme.messagePadding;
|
|
127
|
+
if (theme?.borderRadius)
|
|
128
|
+
themeStyles['--chat-border-radius'] = theme.borderRadius;
|
|
129
|
+
if (theme?.messageBorderRadius)
|
|
130
|
+
themeStyles['--chat-message-border-radius'] = theme.messageBorderRadius;
|
|
131
|
+
if (theme?.inputBorderRadius)
|
|
132
|
+
themeStyles['--chat-input-border-radius'] = theme.inputBorderRadius;
|
|
133
|
+
if (theme?.borderWidth)
|
|
134
|
+
themeStyles['--chat-border-width'] = theme.borderWidth;
|
|
135
|
+
if (theme?.boxShadow)
|
|
136
|
+
themeStyles['--chat-box-shadow'] = theme.boxShadow;
|
|
137
|
+
if (theme?.messageBoxShadow)
|
|
138
|
+
themeStyles['--chat-message-box-shadow'] = theme.messageBoxShadow;
|
|
139
|
+
if (theme?.inputBoxShadow)
|
|
140
|
+
themeStyles['--chat-input-box-shadow'] = theme.inputBoxShadow;
|
|
141
|
+
if (maxHeight || theme?.maxHeight)
|
|
142
|
+
themeStyles['--chat-max-height'] = maxHeight || theme.maxHeight || '';
|
|
143
|
+
if (theme?.maxWidth)
|
|
144
|
+
themeStyles['--chat-max-width'] = theme.maxWidth;
|
|
145
|
+
if (theme?.messageMaxWidth)
|
|
146
|
+
themeStyles['--chat-message-max-width'] = theme.messageMaxWidth;
|
|
147
|
+
if (theme?.avatarSize)
|
|
148
|
+
themeStyles['--chat-avatar-size'] = theme.avatarSize;
|
|
149
|
+
if (theme?.transitionDuration)
|
|
150
|
+
themeStyles['--chat-transition-duration'] = theme.transitionDuration;
|
|
151
|
+
if (theme?.animationDuration)
|
|
152
|
+
themeStyles['--chat-animation-duration'] = theme.animationDuration;
|
|
153
|
+
if (theme?.scrollbarWidth)
|
|
154
|
+
themeStyles['--chat-scrollbar-width'] = theme.scrollbarWidth;
|
|
155
|
+
if (theme?.scrollbarColor)
|
|
156
|
+
themeStyles['--chat-scrollbar-color'] = theme.scrollbarColor;
|
|
157
|
+
if (theme?.scrollbarTrackColor)
|
|
158
|
+
themeStyles['--chat-scrollbar-track-color'] = theme.scrollbarTrackColor;
|
|
159
|
+
// Build data attributes
|
|
160
|
+
const containerDataAttributes = {
|
|
161
|
+
'data-chat-ui': 'true',
|
|
162
|
+
...dataAttributes,
|
|
163
|
+
};
|
|
164
|
+
return (_jsxs("div", { className: `chat-container ${className}`, style: themeStyles, ...containerDataAttributes, children: [theme?.customCSS && (_jsx("style", { dangerouslySetInnerHTML: { __html: theme.customCSS } })), _jsxs("div", { className: `chat-messages-container ${messagesContainerClassName}`, children: [messages.length === 0 && (_jsx("div", { className: `chat-empty-state ${emptyStateClassName}`, children: "Start a conversation..." })), messages.map(renderMessage), isLoading && (_jsxs("div", { className: `chat-message chat-message-assistant ${messageClassName}`, children: [renderAvatar('assistant'), _jsx("div", { className: "chat-message-content chat-message-content-assistant", children: _jsx("div", { className: "chat-message-text", children: "Thinking..." }) })] }))] }), error && (_jsxs("div", { className: `chat-error ${errorClassName}`, children: ["Error: ", error.message] })), _jsx("div", { className: `chat-input-container ${inputContainerClassName}`, children: customInputRenderer ? (customInputRenderer({
|
|
165
|
+
value: input,
|
|
166
|
+
onChange: setInput,
|
|
167
|
+
onSend: () => sendMessage(input),
|
|
168
|
+
disabled: disabled || isLoading,
|
|
169
|
+
})) : (_jsxs("form", { onSubmit: handleSubmit, className: "chat-input-form", children: [_jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: placeholder, disabled: disabled || isLoading, className: `chat-input ${inputClassName}` }), _jsx("button", { type: "submit", disabled: disabled || isLoading || !input.trim(), className: `chat-send-button ${buttonClassName}`, children: "Send" })] })) })] }));
|
|
170
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../adapters/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChatConfig, Message } from '../../core/src/types';
|
|
2
|
+
export interface UseChatReturn {
|
|
3
|
+
messages: Message[];
|
|
4
|
+
input: string;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error: Error | null;
|
|
7
|
+
sendMessage: (content: string) => Promise<void>;
|
|
8
|
+
setInput: (value: string) => void;
|
|
9
|
+
clearMessages: () => void;
|
|
10
|
+
retryLastMessage: () => Promise<void>;
|
|
11
|
+
addMessage: (message: Message) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function useChat(config: ChatConfig): UseChatReturn;
|
|
14
|
+
//# sourceMappingURL=useChat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../../adapters/react/useChat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CA0EzD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { ChatEngine } from '../../core/src/ChatEngine';
|
|
3
|
+
export function useChat(config) {
|
|
4
|
+
const engineRef = useRef(null);
|
|
5
|
+
const [messages, setMessages] = useState([]);
|
|
6
|
+
const [input, setInput] = useState('');
|
|
7
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const engine = new ChatEngine(config);
|
|
11
|
+
engineRef.current = engine;
|
|
12
|
+
// Set initial state
|
|
13
|
+
setMessages(engine.getMessages());
|
|
14
|
+
setInput(engine.getInput());
|
|
15
|
+
setIsLoading(engine.getIsLoading());
|
|
16
|
+
setError(engine.getError());
|
|
17
|
+
// Subscribe to events
|
|
18
|
+
const unsubscribeMessages = engine.on('messagesChange', (msgs) => {
|
|
19
|
+
setMessages(msgs || []);
|
|
20
|
+
});
|
|
21
|
+
const unsubscribeInput = engine.on('inputChange', (value) => {
|
|
22
|
+
setInput(value || '');
|
|
23
|
+
});
|
|
24
|
+
const unsubscribeLoading = engine.on('loadingChange', (loading) => {
|
|
25
|
+
setIsLoading(loading || false);
|
|
26
|
+
});
|
|
27
|
+
const unsubscribeError = engine.on('error', (err) => {
|
|
28
|
+
setError(err || null);
|
|
29
|
+
});
|
|
30
|
+
return () => {
|
|
31
|
+
unsubscribeMessages();
|
|
32
|
+
unsubscribeInput();
|
|
33
|
+
unsubscribeLoading();
|
|
34
|
+
unsubscribeError();
|
|
35
|
+
engine.destroy();
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
const sendMessage = useCallback(async (content) => {
|
|
39
|
+
await engineRef.current?.sendMessage(content);
|
|
40
|
+
}, []);
|
|
41
|
+
const setInputValue = useCallback((value) => {
|
|
42
|
+
engineRef.current?.setInput(value);
|
|
43
|
+
}, []);
|
|
44
|
+
const clearMessages = useCallback(() => {
|
|
45
|
+
engineRef.current?.clearMessages();
|
|
46
|
+
}, []);
|
|
47
|
+
const retryLastMessage = useCallback(async () => {
|
|
48
|
+
await engineRef.current?.retryLastMessage();
|
|
49
|
+
}, []);
|
|
50
|
+
const addMessage = useCallback((message) => {
|
|
51
|
+
engineRef.current?.addMessage(message);
|
|
52
|
+
}, []);
|
|
53
|
+
return {
|
|
54
|
+
messages,
|
|
55
|
+
input,
|
|
56
|
+
isLoading,
|
|
57
|
+
error,
|
|
58
|
+
sendMessage,
|
|
59
|
+
setInput: setInputValue,
|
|
60
|
+
clearMessages,
|
|
61
|
+
retryLastMessage,
|
|
62
|
+
addMessage,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatConfig, Message } from '../../core/src/types';
|
|
2
|
+
export interface CreateChatReturn {
|
|
3
|
+
messages: () => Message[];
|
|
4
|
+
input: () => string;
|
|
5
|
+
isLoading: () => boolean;
|
|
6
|
+
error: () => Error | null;
|
|
7
|
+
sendMessage: (content: string) => Promise<void>;
|
|
8
|
+
setInput: (value: string) => void;
|
|
9
|
+
clearMessages: () => void;
|
|
10
|
+
retryLastMessage: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createChat(config: ChatConfig): CreateChatReturn;
|
|
13
|
+
//# sourceMappingURL=createChat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createChat.d.ts","sourceRoot":"","sources":["../../../adapters/solid/createChat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,OAAO,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAqC/D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
2
|
+
import { ChatEngine } from '../../core/src/ChatEngine';
|
|
3
|
+
export function createChat(config) {
|
|
4
|
+
const engine = new ChatEngine(config);
|
|
5
|
+
const [messages, setMessages] = createSignal(engine.getMessages());
|
|
6
|
+
const [input, setInput] = createSignal(engine.getInput());
|
|
7
|
+
const [isLoading, setIsLoading] = createSignal(engine.getIsLoading());
|
|
8
|
+
const [error, setError] = createSignal(engine.getError());
|
|
9
|
+
engine.on('messagesChange', (msgs) => {
|
|
10
|
+
setMessages(msgs || []);
|
|
11
|
+
});
|
|
12
|
+
engine.on('inputChange', (value) => {
|
|
13
|
+
setInput(value || '');
|
|
14
|
+
});
|
|
15
|
+
engine.on('loadingChange', (loading) => {
|
|
16
|
+
setIsLoading(loading || false);
|
|
17
|
+
});
|
|
18
|
+
engine.on('error', (err) => {
|
|
19
|
+
setError(err || null);
|
|
20
|
+
});
|
|
21
|
+
onCleanup(() => {
|
|
22
|
+
engine.destroy();
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
messages,
|
|
26
|
+
input,
|
|
27
|
+
isLoading,
|
|
28
|
+
error,
|
|
29
|
+
sendMessage: (content) => engine.sendMessage(content),
|
|
30
|
+
setInput: (value) => engine.setInput(value),
|
|
31
|
+
clearMessages: () => engine.clearMessages(),
|
|
32
|
+
retryLastMessage: () => engine.retryLastMessage(),
|
|
33
|
+
};
|
|
34
|
+
}
|