@aichatbot-saas/react 0.1.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 +114 -0
- package/dist/index.cjs +408 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +386 -0
- package/package.json +66 -0
- package/src/AIChatbot.tsx +248 -0
- package/src/components/MessageBubble.tsx +107 -0
- package/src/components/SuggestionChips.tsx +58 -0
- package/src/components/TypingIndicator.tsx +26 -0
- package/src/index.ts +47 -0
- package/src/useChatController.ts +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AIChatbot
|
|
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,114 @@
|
|
|
1
|
+
# @aichatbot-saas/react
|
|
2
|
+
|
|
3
|
+
Native, in-page **React (web)** chat UI component for the [AIChatbot](https://github.com/piyushshri01/chat-bot-saas) SaaS.
|
|
4
|
+
|
|
5
|
+
Unlike the drop-in `chatbot.js` floating bubble, `<AIChatbot>` is a real inline
|
|
6
|
+
chat panel you embed wherever you want — it fills its container and the host
|
|
7
|
+
controls size and placement. It's a thin UI binding over the framework-agnostic
|
|
8
|
+
[`@aichatbot-saas/core`](https://www.npmjs.com/package/@aichatbot-saas/core)
|
|
9
|
+
(REST client + theme model + headless `ChatController`), so all behavior lives in
|
|
10
|
+
one shared place and this package just renders that state.
|
|
11
|
+
|
|
12
|
+
- Themed entirely from your backend config (and overridable locally).
|
|
13
|
+
- 100% inline styles — no global CSS, no external UI libraries, never clobbers host styles.
|
|
14
|
+
- SSR-safe: nothing touches `window`/`document` at module load or during render.
|
|
15
|
+
- Strict TypeScript, React 17/18/19 (`react` + `react-dom` ≥ 17).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i @aichatbot-saas/react
|
|
21
|
+
# pnpm add @aichatbot-saas/react | yarn add @aichatbot-saas/react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Installing this package also pulls in its dependency
|
|
25
|
+
[`@aichatbot-saas/core`](https://www.npmjs.com/package/@aichatbot-saas/core).
|
|
26
|
+
`react` and `react-dom` are peer dependencies — bring your own.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
The component fills its parent, so wrap it in a sized box:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { AIChatbot } from '@aichatbot-saas/react';
|
|
34
|
+
|
|
35
|
+
export function Support() {
|
|
36
|
+
return (
|
|
37
|
+
<div style={{ width: 380, height: 560 }}>
|
|
38
|
+
<AIChatbot apiKey="pk_live_xxx" apiUrl="https://api.you.com" />
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Only `apiKey` is required. Everything else (bot name, greeting, suggestions,
|
|
45
|
+
languages, theme) is fetched from the backend on mount.
|
|
46
|
+
|
|
47
|
+
## Props
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Required | Description |
|
|
50
|
+
|-------------|-----------------------|:--------:|-----------------------------------------------------------------------------|
|
|
51
|
+
| `apiKey` | `string` | yes | Publishable API key, sent as `X-API-Key`. |
|
|
52
|
+
| `apiUrl` | `string` | no | Backend base URL. Defaults to same-origin. |
|
|
53
|
+
| `language` | `string` | no | Force a language. Otherwise `config.languages[0]` → `"en"`. |
|
|
54
|
+
| `theme` | `Partial<ChatTheme>` | no | Local theme overrides (highest precedence). See below. |
|
|
55
|
+
| `sessionId` | `string` | no | Stable device/session id forwarded to the backend for threading. |
|
|
56
|
+
| `className` | `string` | no | Extra class on the root panel. |
|
|
57
|
+
| `style` | `React.CSSProperties` | no | Inline styles merged onto the root panel (e.g. `width`/`height`/`border`). |
|
|
58
|
+
|
|
59
|
+
## Theming
|
|
60
|
+
|
|
61
|
+
Theme precedence is **client override → backend config → built-in default**.
|
|
62
|
+
Override any [`ChatTheme`](https://www.npmjs.com/package/@aichatbot-saas/core)
|
|
63
|
+
token via the `theme` prop:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<AIChatbot
|
|
67
|
+
apiKey="pk_live_xxx"
|
|
68
|
+
apiUrl="https://api.you.com"
|
|
69
|
+
theme={{
|
|
70
|
+
primaryColor: '#7c3aed', // launcher/header/user bubble/send/chips
|
|
71
|
+
botBubbleColor: '#f3f4f6',
|
|
72
|
+
bubbleRadius: 18,
|
|
73
|
+
inputRadius: 24,
|
|
74
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Next.js / SSR
|
|
80
|
+
|
|
81
|
+
`<AIChatbot>` is SSR-safe — it renders a stable initial snapshot on the server
|
|
82
|
+
and only performs network/storage I/O inside effects on the client. In the
|
|
83
|
+
App Router it's a **Client Component**, so render it from one and add the
|
|
84
|
+
directive at the top of that file:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
'use client';
|
|
88
|
+
import { AIChatbot } from '@aichatbot-saas/react';
|
|
89
|
+
|
|
90
|
+
export default function ChatPanel() {
|
|
91
|
+
return (
|
|
92
|
+
<div style={{ width: 380, height: 560 }}>
|
|
93
|
+
<AIChatbot apiKey={process.env.NEXT_PUBLIC_AICHATBOT_KEY!} apiUrl="https://api.you.com" />
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Advanced: headless usage
|
|
100
|
+
|
|
101
|
+
The full core surface is re-exported for convenience, so you can build a custom
|
|
102
|
+
UI without depending on `@aichatbot-saas/core` directly:
|
|
103
|
+
|
|
104
|
+
- `useChatController(options)` → `{ state, controller }` — the hook the component
|
|
105
|
+
uses internally (wraps a `ChatController` in `useSyncExternalStore`).
|
|
106
|
+
- `AIChatbotClient` — the headless REST client (`fetchConfig`, `fetchSuggestions`,
|
|
107
|
+
`sendMessage`, `sendFeedback`).
|
|
108
|
+
- `ChatController` / `createChatController`, `resolveTheme`, `DEFAULT_THEME`.
|
|
109
|
+
- The presentational pieces: `MessageBubble`, `SuggestionChips`, `TypingIndicator`.
|
|
110
|
+
- Types: `ChatTheme`, `ChatMessage`, `ChatConfig`, `ChatResponse`, `ChatState`, etc.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT © 2026 AIChatbot
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AIChatbot: () => AIChatbot,
|
|
24
|
+
AIChatbotClient: () => import_core2.AIChatbotClient,
|
|
25
|
+
ChatController: () => import_core2.ChatController,
|
|
26
|
+
DEFAULT_THEME: () => import_core2.DEFAULT_THEME,
|
|
27
|
+
MessageBubble: () => MessageBubble,
|
|
28
|
+
SuggestionChips: () => SuggestionChips,
|
|
29
|
+
TypingIndicator: () => TypingIndicator,
|
|
30
|
+
createChatController: () => import_core2.createChatController,
|
|
31
|
+
default: () => AIChatbot_default,
|
|
32
|
+
resolveTheme: () => import_core2.resolveTheme,
|
|
33
|
+
useChatController: () => useChatController
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/AIChatbot.tsx
|
|
38
|
+
var import_react2 = require("react");
|
|
39
|
+
|
|
40
|
+
// src/useChatController.ts
|
|
41
|
+
var import_react = require("react");
|
|
42
|
+
var import_core = require("@aichatbot-saas/core");
|
|
43
|
+
function useChatController(options) {
|
|
44
|
+
var _a;
|
|
45
|
+
const { apiKey, apiUrl, language, theme, sessionId } = options;
|
|
46
|
+
const themeKey = (0, import_react.useMemo)(() => JSON.stringify(theme != null ? theme : {}), [theme]);
|
|
47
|
+
const controller = (0, import_react.useMemo)(
|
|
48
|
+
() => (0, import_core.createChatController)({
|
|
49
|
+
apiKey,
|
|
50
|
+
apiUrl,
|
|
51
|
+
language,
|
|
52
|
+
theme,
|
|
53
|
+
sessionId
|
|
54
|
+
}),
|
|
55
|
+
// theme is captured via its serialized key; eslint can't see that.
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
[apiKey, apiUrl, language, themeKey, sessionId]
|
|
58
|
+
);
|
|
59
|
+
const serverSnapshotRef = (0, import_react.useRef)(
|
|
60
|
+
null
|
|
61
|
+
);
|
|
62
|
+
if (((_a = serverSnapshotRef.current) == null ? void 0 : _a.controller) !== controller) {
|
|
63
|
+
serverSnapshotRef.current = { controller, state: controller.getState() };
|
|
64
|
+
}
|
|
65
|
+
const getServerSnapshot = () => serverSnapshotRef.current.state;
|
|
66
|
+
const state = (0, import_react.useSyncExternalStore)(
|
|
67
|
+
controller.subscribe,
|
|
68
|
+
controller.getState,
|
|
69
|
+
getServerSnapshot
|
|
70
|
+
);
|
|
71
|
+
(0, import_react.useEffect)(() => {
|
|
72
|
+
void controller.start();
|
|
73
|
+
return () => {
|
|
74
|
+
controller.destroy();
|
|
75
|
+
};
|
|
76
|
+
}, [controller]);
|
|
77
|
+
return { state, controller };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/components/MessageBubble.tsx
|
|
81
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
82
|
+
function MessageBubble({ message, theme, onFeedback }) {
|
|
83
|
+
const isUser = message.role === "user";
|
|
84
|
+
const canRate = !isUser && !!message.serverId && !message.error;
|
|
85
|
+
const rowStyle = {
|
|
86
|
+
display: "flex",
|
|
87
|
+
flexDirection: "column",
|
|
88
|
+
alignItems: isUser ? "flex-end" : "flex-start",
|
|
89
|
+
margin: "8px 0"
|
|
90
|
+
};
|
|
91
|
+
const bubbleStyle = {
|
|
92
|
+
maxWidth: "80%",
|
|
93
|
+
padding: "9px 13px",
|
|
94
|
+
borderRadius: theme.bubbleRadius,
|
|
95
|
+
fontSize: 14,
|
|
96
|
+
lineHeight: 1.45,
|
|
97
|
+
whiteSpace: "pre-wrap",
|
|
98
|
+
wordWrap: "break-word",
|
|
99
|
+
overflowWrap: "anywhere",
|
|
100
|
+
...isUser ? {
|
|
101
|
+
background: theme.userBubbleColor,
|
|
102
|
+
color: theme.userBubbleTextColor,
|
|
103
|
+
borderBottomRightRadius: 4
|
|
104
|
+
} : {
|
|
105
|
+
background: theme.botBubbleColor,
|
|
106
|
+
color: theme.botBubbleTextColor,
|
|
107
|
+
border: `1px solid ${theme.botBubbleBorderColor}`,
|
|
108
|
+
borderBottomLeftRadius: 4
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: bubbleStyle, children: message.text }),
|
|
113
|
+
canRate && (message.feedback ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 4, fontSize: 13, color: theme.mutedTextColor }, children: "Thanks!" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FeedbackButtons, { message, theme, onFeedback }))
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
function FeedbackButtons({ message, theme, onFeedback }) {
|
|
117
|
+
const btnStyle = {
|
|
118
|
+
background: "none",
|
|
119
|
+
border: "none",
|
|
120
|
+
cursor: "pointer",
|
|
121
|
+
fontSize: 14,
|
|
122
|
+
padding: "0 2px",
|
|
123
|
+
color: theme.mutedTextColor,
|
|
124
|
+
opacity: 0.7,
|
|
125
|
+
lineHeight: 1
|
|
126
|
+
};
|
|
127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 4, display: "flex", gap: 2 }, children: [
|
|
128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
type: "button",
|
|
132
|
+
title: "Helpful",
|
|
133
|
+
"aria-label": "Helpful",
|
|
134
|
+
style: btnStyle,
|
|
135
|
+
onClick: () => onFeedback == null ? void 0 : onFeedback(message, "positive"),
|
|
136
|
+
children: "\u{1F44D}"
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
title: "Not helpful",
|
|
144
|
+
"aria-label": "Not helpful",
|
|
145
|
+
style: btnStyle,
|
|
146
|
+
onClick: () => onFeedback == null ? void 0 : onFeedback(message, "negative"),
|
|
147
|
+
children: "\u{1F44E}"
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/components/SuggestionChips.tsx
|
|
154
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
155
|
+
function SuggestionChips({
|
|
156
|
+
suggestions,
|
|
157
|
+
theme,
|
|
158
|
+
disabled = false,
|
|
159
|
+
onSelect
|
|
160
|
+
}) {
|
|
161
|
+
if (!suggestions.length) return null;
|
|
162
|
+
const rowStyle = {
|
|
163
|
+
display: "flex",
|
|
164
|
+
flexWrap: "wrap",
|
|
165
|
+
gap: 6,
|
|
166
|
+
margin: "6px 0 2px"
|
|
167
|
+
};
|
|
168
|
+
const chipStyle = {
|
|
169
|
+
background: theme.backgroundColor,
|
|
170
|
+
border: `1px solid ${theme.primaryColor}`,
|
|
171
|
+
color: theme.primaryColor,
|
|
172
|
+
borderRadius: 16,
|
|
173
|
+
padding: "6px 12px",
|
|
174
|
+
fontSize: 13,
|
|
175
|
+
cursor: disabled ? "default" : "pointer",
|
|
176
|
+
opacity: disabled ? 0.6 : 1,
|
|
177
|
+
fontFamily: "inherit",
|
|
178
|
+
lineHeight: 1.2
|
|
179
|
+
};
|
|
180
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: rowStyle, children: suggestions.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
181
|
+
"button",
|
|
182
|
+
{
|
|
183
|
+
type: "button",
|
|
184
|
+
style: chipStyle,
|
|
185
|
+
disabled,
|
|
186
|
+
onClick: () => onSelect(label),
|
|
187
|
+
children: label
|
|
188
|
+
},
|
|
189
|
+
`${i}-${label}`
|
|
190
|
+
)) });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/components/TypingIndicator.tsx
|
|
194
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
195
|
+
function TypingIndicator({ theme, botName }) {
|
|
196
|
+
const style = {
|
|
197
|
+
fontSize: 13,
|
|
198
|
+
color: theme.mutedTextColor,
|
|
199
|
+
fontStyle: "italic",
|
|
200
|
+
margin: "6px 2px"
|
|
201
|
+
};
|
|
202
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style, "aria-live": "polite", children: (botName || "Assistant") + " is typing\u2026" });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/AIChatbot.tsx
|
|
206
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
207
|
+
function AIChatbot({
|
|
208
|
+
apiKey,
|
|
209
|
+
apiUrl,
|
|
210
|
+
language,
|
|
211
|
+
theme,
|
|
212
|
+
sessionId,
|
|
213
|
+
className,
|
|
214
|
+
style
|
|
215
|
+
}) {
|
|
216
|
+
var _a, _b;
|
|
217
|
+
const { state, controller } = useChatController({
|
|
218
|
+
apiKey,
|
|
219
|
+
apiUrl,
|
|
220
|
+
language,
|
|
221
|
+
theme,
|
|
222
|
+
sessionId
|
|
223
|
+
});
|
|
224
|
+
const [draft, setDraft] = (0, import_react2.useState)("");
|
|
225
|
+
const scrollRef = (0, import_react2.useRef)(null);
|
|
226
|
+
const headingId = (0, import_react2.useId)();
|
|
227
|
+
const t = state.theme;
|
|
228
|
+
const botName = ((_a = state.config) == null ? void 0 : _a.bot_name) || "Assistant";
|
|
229
|
+
const iconUrl = (_b = state.config) == null ? void 0 : _b.bot_icon_url;
|
|
230
|
+
(0, import_react2.useEffect)(() => {
|
|
231
|
+
const node = scrollRef.current;
|
|
232
|
+
if (node) node.scrollTop = node.scrollHeight;
|
|
233
|
+
}, [state.messages, state.isTyping]);
|
|
234
|
+
const handleSubmit = (0, import_react2.useCallback)(
|
|
235
|
+
(e) => {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
const value = draft;
|
|
238
|
+
setDraft("");
|
|
239
|
+
void controller.send(value);
|
|
240
|
+
},
|
|
241
|
+
[controller, draft]
|
|
242
|
+
);
|
|
243
|
+
const handleSuggestion = (0, import_react2.useCallback)(
|
|
244
|
+
(text) => {
|
|
245
|
+
void controller.sendSuggestion(text);
|
|
246
|
+
},
|
|
247
|
+
[controller]
|
|
248
|
+
);
|
|
249
|
+
const panelStyle = {
|
|
250
|
+
display: "flex",
|
|
251
|
+
flexDirection: "column",
|
|
252
|
+
width: "100%",
|
|
253
|
+
height: "100%",
|
|
254
|
+
minHeight: 0,
|
|
255
|
+
boxSizing: "border-box",
|
|
256
|
+
background: t.backgroundColor,
|
|
257
|
+
color: t.botBubbleTextColor,
|
|
258
|
+
fontFamily: t.fontFamily,
|
|
259
|
+
fontSize: 14,
|
|
260
|
+
overflow: "hidden",
|
|
261
|
+
...style
|
|
262
|
+
};
|
|
263
|
+
const headerStyle = {
|
|
264
|
+
background: t.primaryColor,
|
|
265
|
+
color: t.onPrimaryColor,
|
|
266
|
+
padding: "14px 16px",
|
|
267
|
+
fontWeight: 600,
|
|
268
|
+
display: "flex",
|
|
269
|
+
alignItems: "center",
|
|
270
|
+
gap: 10,
|
|
271
|
+
flex: "0 0 auto"
|
|
272
|
+
};
|
|
273
|
+
const avatarStyle = {
|
|
274
|
+
width: 28,
|
|
275
|
+
height: 28,
|
|
276
|
+
borderRadius: "50%",
|
|
277
|
+
objectFit: "cover",
|
|
278
|
+
flex: "0 0 auto",
|
|
279
|
+
background: "rgba(255,255,255,0.2)"
|
|
280
|
+
};
|
|
281
|
+
const bodyStyle = {
|
|
282
|
+
flex: "1 1 auto",
|
|
283
|
+
minHeight: 0,
|
|
284
|
+
overflowY: "auto",
|
|
285
|
+
padding: 14,
|
|
286
|
+
background: t.surfaceColor
|
|
287
|
+
};
|
|
288
|
+
const footerStyle = {
|
|
289
|
+
display: "flex",
|
|
290
|
+
gap: 6,
|
|
291
|
+
borderTop: `1px solid ${t.botBubbleBorderColor}`,
|
|
292
|
+
padding: 8,
|
|
293
|
+
background: t.backgroundColor,
|
|
294
|
+
flex: "0 0 auto"
|
|
295
|
+
};
|
|
296
|
+
const inputStyle = {
|
|
297
|
+
flex: 1,
|
|
298
|
+
minWidth: 0,
|
|
299
|
+
border: `1px solid ${t.botBubbleBorderColor}`,
|
|
300
|
+
borderRadius: t.inputRadius,
|
|
301
|
+
padding: "9px 14px",
|
|
302
|
+
fontSize: 14,
|
|
303
|
+
fontFamily: "inherit",
|
|
304
|
+
outline: "none",
|
|
305
|
+
color: t.botBubbleTextColor,
|
|
306
|
+
background: t.backgroundColor
|
|
307
|
+
};
|
|
308
|
+
const sendDisabled = state.sending || state.status !== "ready" || draft.trim().length === 0;
|
|
309
|
+
const sendStyle = {
|
|
310
|
+
background: t.primaryColor,
|
|
311
|
+
color: t.onPrimaryColor,
|
|
312
|
+
border: "none",
|
|
313
|
+
borderRadius: t.inputRadius,
|
|
314
|
+
padding: "0 16px",
|
|
315
|
+
cursor: sendDisabled ? "default" : "pointer",
|
|
316
|
+
opacity: sendDisabled ? 0.6 : 1,
|
|
317
|
+
fontSize: 14,
|
|
318
|
+
fontFamily: "inherit",
|
|
319
|
+
flex: "0 0 auto"
|
|
320
|
+
};
|
|
321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
322
|
+
"div",
|
|
323
|
+
{
|
|
324
|
+
className,
|
|
325
|
+
style: panelStyle,
|
|
326
|
+
role: "region",
|
|
327
|
+
"aria-label": `${botName} chat`,
|
|
328
|
+
"aria-labelledby": headingId,
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { style: headerStyle, children: [
|
|
331
|
+
iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: iconUrl, alt: "", style: avatarStyle }) : null,
|
|
332
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: headingId, children: botName })
|
|
333
|
+
] }),
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: bodyStyle, ref: scrollRef, children: [
|
|
335
|
+
state.status === "loading" && state.messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: t.mutedTextColor, fontSize: 13, padding: 4 }, children: "Loading\u2026" }) : null,
|
|
336
|
+
state.messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
337
|
+
MessageBubble,
|
|
338
|
+
{
|
|
339
|
+
message,
|
|
340
|
+
theme: t,
|
|
341
|
+
onFeedback: controller.submitFeedback.bind(controller)
|
|
342
|
+
},
|
|
343
|
+
message.id
|
|
344
|
+
)),
|
|
345
|
+
state.isTyping ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TypingIndicator, { theme: t, botName }) : null,
|
|
346
|
+
state.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
role: "alert",
|
|
350
|
+
style: {
|
|
351
|
+
background: t.botBubbleColor,
|
|
352
|
+
color: t.botBubbleTextColor,
|
|
353
|
+
border: `1px solid ${t.botBubbleBorderColor}`,
|
|
354
|
+
borderRadius: t.bubbleRadius,
|
|
355
|
+
padding: "9px 13px",
|
|
356
|
+
fontSize: 14,
|
|
357
|
+
margin: "8px 0"
|
|
358
|
+
},
|
|
359
|
+
children: state.error || "Sorry, something went wrong loading the chat."
|
|
360
|
+
}
|
|
361
|
+
) : null,
|
|
362
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
363
|
+
SuggestionChips,
|
|
364
|
+
{
|
|
365
|
+
suggestions: state.suggestions,
|
|
366
|
+
theme: t,
|
|
367
|
+
disabled: state.sending,
|
|
368
|
+
onSelect: handleSuggestion
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
] }),
|
|
372
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { style: footerStyle, onSubmit: handleSubmit, children: [
|
|
373
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
374
|
+
"input",
|
|
375
|
+
{
|
|
376
|
+
style: inputStyle,
|
|
377
|
+
type: "text",
|
|
378
|
+
value: draft,
|
|
379
|
+
maxLength: 4e3,
|
|
380
|
+
placeholder: "Type your message\u2026",
|
|
381
|
+
"aria-label": "Message",
|
|
382
|
+
onChange: (e) => setDraft(e.target.value),
|
|
383
|
+
disabled: state.status === "error"
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", style: sendStyle, disabled: sendDisabled, children: "Send" })
|
|
387
|
+
] })
|
|
388
|
+
]
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
var AIChatbot_default = AIChatbot;
|
|
393
|
+
|
|
394
|
+
// src/index.ts
|
|
395
|
+
var import_core2 = require("@aichatbot-saas/core");
|
|
396
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
397
|
+
0 && (module.exports = {
|
|
398
|
+
AIChatbot,
|
|
399
|
+
AIChatbotClient,
|
|
400
|
+
ChatController,
|
|
401
|
+
DEFAULT_THEME,
|
|
402
|
+
MessageBubble,
|
|
403
|
+
SuggestionChips,
|
|
404
|
+
TypingIndicator,
|
|
405
|
+
createChatController,
|
|
406
|
+
resolveTheme,
|
|
407
|
+
useChatController
|
|
408
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { ChatTheme, ChatState, ChatController, ChatMessage, FeedbackValue } from '@aichatbot-saas/core';
|
|
4
|
+
export { AIChatbotClient, ChatConfig, ChatController, ChatControllerOptions, ChatMessage, ChatResponse, ChatRole, ChatState, ChatStatus, ChatTheme, DEFAULT_THEME, FeedbackValue, Suggestion, SuggestionInput, createChatController, resolveTheme } from '@aichatbot-saas/core';
|
|
5
|
+
|
|
6
|
+
interface AIChatbotProps {
|
|
7
|
+
/** Publishable API key (required). */
|
|
8
|
+
apiKey: string;
|
|
9
|
+
/** Backend base URL. Defaults to same-origin. */
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
/** Force a language; otherwise config.languages[0] -> "en". */
|
|
12
|
+
language?: string;
|
|
13
|
+
/** Local theme overrides (highest precedence: client -> backend -> default). */
|
|
14
|
+
theme?: Partial<ChatTheme>;
|
|
15
|
+
/** Stable device/session id forwarded to the backend for threading. */
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
/** Extra class on the root panel. */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Extra inline styles merged onto the root panel (e.g. width/height). */
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `<AIChatbot>` — a native, inline, embeddable chat panel for the web.
|
|
24
|
+
*
|
|
25
|
+
* It fills its container, so the host controls size and placement (drop it into
|
|
26
|
+
* a sized `<div>`). All behavior (config load, greeting, suggestions, send flow,
|
|
27
|
+
* conversation persistence, feedback, error handling) lives in the shared
|
|
28
|
+
* headless controller; this component only renders that state and wires events.
|
|
29
|
+
*
|
|
30
|
+
* Styling is 100% inline and derived from the resolved theme — no global CSS, no
|
|
31
|
+
* external UI libs — so it never clobbers host styles and is SSR-safe.
|
|
32
|
+
*
|
|
33
|
+
* In Next.js / RSC, render it from a Client Component (add `"use client"`).
|
|
34
|
+
*/
|
|
35
|
+
declare function AIChatbot({ apiKey, apiUrl, language, theme, sessionId, className, style, }: AIChatbotProps): react.JSX.Element;
|
|
36
|
+
|
|
37
|
+
/** Options accepted by {@link useChatController}. Mirrors ChatControllerOptions. */
|
|
38
|
+
interface UseChatControllerOptions {
|
|
39
|
+
apiKey: string;
|
|
40
|
+
apiUrl?: string;
|
|
41
|
+
language?: string;
|
|
42
|
+
theme?: Partial<ChatTheme>;
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
}
|
|
45
|
+
interface UseChatControllerResult {
|
|
46
|
+
state: ChatState;
|
|
47
|
+
controller: ChatController;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates and drives a headless {@link ChatController}, exposing its immutable
|
|
51
|
+
* state to React via `useSyncExternalStore` (React 18+, concurrent-safe).
|
|
52
|
+
*
|
|
53
|
+
* The controller is memoized on the identity-defining inputs (apiKey, apiUrl,
|
|
54
|
+
* language, serialized theme, sessionId); when any of those change a fresh
|
|
55
|
+
* controller is created, started, and the previous one is destroyed.
|
|
56
|
+
*
|
|
57
|
+
* SSR-safe: the controller is constructed lazily and never touches
|
|
58
|
+
* window/document during render. `start()` (which performs network/storage I/O)
|
|
59
|
+
* runs only inside an effect, so it never executes on the server.
|
|
60
|
+
*/
|
|
61
|
+
declare function useChatController(options: UseChatControllerOptions): UseChatControllerResult;
|
|
62
|
+
|
|
63
|
+
interface MessageBubbleProps {
|
|
64
|
+
message: ChatMessage;
|
|
65
|
+
theme: ChatTheme;
|
|
66
|
+
/**
|
|
67
|
+
* Called when the user rates this (bot) message. Only rendered for bot
|
|
68
|
+
* messages that carry a `serverId` (i.e. not the greeting and not error
|
|
69
|
+
* bubbles).
|
|
70
|
+
*/
|
|
71
|
+
onFeedback?: (message: ChatMessage, value: FeedbackValue) => void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A single chat bubble. User messages align right with the primary color; bot
|
|
75
|
+
* messages align left with a bordered white surface. Bot messages eligible for
|
|
76
|
+
* feedback render thumbs up/down beneath the bubble (replaced by "Thanks!" once
|
|
77
|
+
* rated). All styling is inline and derived from the theme.
|
|
78
|
+
*/
|
|
79
|
+
declare function MessageBubble({ message, theme, onFeedback }: MessageBubbleProps): react.JSX.Element;
|
|
80
|
+
|
|
81
|
+
interface SuggestionChipsProps {
|
|
82
|
+
suggestions: string[];
|
|
83
|
+
theme: ChatTheme;
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
onSelect: (text: string) => void;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A wrapping row of tappable suggestion chips, themed from the primary color.
|
|
89
|
+
* Renders nothing when there are no suggestions.
|
|
90
|
+
*/
|
|
91
|
+
declare function SuggestionChips({ suggestions, theme, disabled, onSelect, }: SuggestionChipsProps): react.JSX.Element | null;
|
|
92
|
+
|
|
93
|
+
interface TypingIndicatorProps {
|
|
94
|
+
theme: ChatTheme;
|
|
95
|
+
/** Bot name to personalize the label, e.g. "Assistant is typing…". */
|
|
96
|
+
botName?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The "<bot> is typing…" indicator shown while the controller is awaiting a
|
|
100
|
+
* reply. Themed via mutedTextColor.
|
|
101
|
+
*/
|
|
102
|
+
declare function TypingIndicator({ theme, botName }: TypingIndicatorProps): react.JSX.Element;
|
|
103
|
+
|
|
104
|
+
export { AIChatbot, type AIChatbotProps, MessageBubble, type MessageBubbleProps, SuggestionChips, type SuggestionChipsProps, TypingIndicator, type TypingIndicatorProps, type UseChatControllerOptions, type UseChatControllerResult, AIChatbot as default, useChatController };
|