@chatbotkit/react 0.2.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/CHANGELOG.md +12 -0
- package/README.md +136 -0
- package/dist/cjs/components/AutoTextarea.cjs +32 -0
- package/dist/cjs/components/AutoTextarea.d.ts +14 -0
- package/dist/cjs/hooks/useConversationManager.cjs +306 -0
- package/dist/cjs/hooks/useConversationManager.d.ts +67 -0
- package/dist/cjs/index.cjs +10 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/utils/string.cjs +56 -0
- package/dist/cjs/utils/string.d.ts +24 -0
- package/dist/esm/components/AutoTextarea.d.ts +14 -0
- package/dist/esm/components/AutoTextarea.js +26 -0
- package/dist/esm/hooks/useConversationManager.d.ts +67 -0
- package/dist/esm/hooks/useConversationManager.js +303 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/utils/string.d.ts +24 -0
- package/dist/esm/utils/string.js +50 -0
- package/package.json +97 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
[](https://twitter.com/chatbotkit)
|
|
2
|
+
[](https://www.npmjs.com/package/@chatbotkit/react)
|
|
3
|
+
[](https://chatbotkit.com)
|
|
4
|
+
|
|
5
|
+
# ChatBotKit React SDK
|
|
6
|
+
|
|
7
|
+
Welcome to the ChatBotKit React SDK. This SDK is a React solution for building conversational AI chatbots with ease. With [ChatBotKit](https://chatbotkit.com), you can quickly develop and deploy chatbots that can interact with users in natural language.
|
|
8
|
+
|
|
9
|
+
## React SDK Features
|
|
10
|
+
|
|
11
|
+
- **Easy setup** - The ChatBotKit React SDK is easy to install and set up. You can have your first chatbot up and running in minutes.
|
|
12
|
+
- **No styles** - ChatBotKit React SDK does not enforce any styles. You can easily style your applications just the way you want them.
|
|
13
|
+
- **Modern** - A modern SDK with built-in support for CommonJS, ECMAScript Modules, async/await, streams and much more.
|
|
14
|
+
- **Customizable** - You can easily customize the chatbot's behavior and responses to fit your specific use case.
|
|
15
|
+
|
|
16
|
+
## ChatBotKit Features
|
|
17
|
+
|
|
18
|
+
- ๐จ **Chat History**: Easily review and reference previous conversations with your bots, ensuring that it has all the information it needs.
|
|
19
|
+
- ๐พ **Custom Datasets**: Manage and organize the data that your chat bots use to respond to user input with bespoke datasets.
|
|
20
|
+
- ๐ฌ **Media File Importing**: Import MP3, MP4, MPEG, WAV and many other media files directly into your your chatbot datasets
|
|
21
|
+
- ๐ **Widget Integration**: Embed ChatBotKit chatbots directly on any website using advanced customization options and theming.
|
|
22
|
+
- ๐ฌ **Slack Bot Integration**: Create and deploy wide-range of Slack bot integrations with just a few click.
|
|
23
|
+
- ๐ฎ **Discord Bot Integration**: Create and deploy wide-range of Discord chat bot with just a few click.
|
|
24
|
+
- ๐ฑ **WhatsApp Bot Integration**: Connect with your audience instantly on the worldโs most popular AI bot platform.
|
|
25
|
+
- ๐บ **Sitemap Integration**: Automatically ingest website content into a searchable knowledge base for your chatbot to reference.
|
|
26
|
+
- ๐ค **GPT-3.5 Support**: State-of-the-art language models to power your conversations.
|
|
27
|
+
- ๐ **GPT-4 Support**: The latest and best language model now can power all chatbots.
|
|
28
|
+
- ๐ฅ **Streaming**: You can turn on and off streaming capabilities for your chatbots.
|
|
29
|
+
- ๐จ **Widget Themes**: Customize the appearance of your chatbot widget with different themes to match your website branding or personal preferences.
|
|
30
|
+
- ๐ก **ChatGPT Extended**: Create your own own ChatGPT bot on variety of skills and domain-specific knowledge.
|
|
31
|
+
- ๐ **Multiple AI Models**: Leverage diverse models from various AI providers to enhance performance and accuracy.
|
|
32
|
+
- ๐ **Data Security**: Ensuring the security of user data, with robust measures in place to protect against unauthorized access.
|
|
33
|
+
- ๐ต **Focus on Privacy**: Get strong privacy controls out of the box. Privide confindence that your customers' data is being handled responsibly.
|
|
34
|
+
- ๐ซ **Content Moderation**: All messages are automatically scanned for abusive content and automatically flagged by the system.
|
|
35
|
+
- ๐ **Semantic Search**: Your chat bot can provide more relevant and accurate responses.
|
|
36
|
+
- โ๏ธ **AI Playgrounds**: Interactive environments that provide a safe and controlled space to experiment, explore, and learn.
|
|
37
|
+
- โ๏ธ **No-Code Platform**: Easily build, customize and deploy chatbots without needing to write any code.
|
|
38
|
+
- ๐ต **Simple Pricing**: Our pricing is straightforward and easy to understand, with no hidden fees or surprises.
|
|
39
|
+
- ๐ฑ **App Platform**: Our platform provides capabilities for building and deploying chatbots for a wide range of applications.
|
|
40
|
+
- ๐ง **Extreme Customization**: Customize your chatbots' responses and behavior based on variety of preferences.
|
|
41
|
+
- ๐ **Expanding Feature Set**: We are constantly adding new features to so you can always stay up-to-date with the latest AI capabilities.
|
|
42
|
+
|
|
43
|
+
## Getting Started
|
|
44
|
+
|
|
45
|
+
To get started with ChatBotKit, follow these simple steps:
|
|
46
|
+
|
|
47
|
+
1. Install the SDK using npm: `npm install @chatbotkit/react`.
|
|
48
|
+
2. Use the SDK to setup or interact with your chatbot.
|
|
49
|
+
|
|
50
|
+
Here is a simple example for the next.js framework. Within the body of our component we invoke the `useConversationManager` React Hook which setups a simple utility to manage the conversation flow.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import { AutoTextarea, useConversationManager } from '@chatbotkit/react'
|
|
54
|
+
|
|
55
|
+
export default function Home() {
|
|
56
|
+
const {
|
|
57
|
+
conversationId,
|
|
58
|
+
setConversationId,
|
|
59
|
+
|
|
60
|
+
token,
|
|
61
|
+
setToken,
|
|
62
|
+
|
|
63
|
+
text,
|
|
64
|
+
setText,
|
|
65
|
+
|
|
66
|
+
messages,
|
|
67
|
+
|
|
68
|
+
thinking,
|
|
69
|
+
|
|
70
|
+
interact,
|
|
71
|
+
} = useConversationManager({ stream: true })
|
|
72
|
+
|
|
73
|
+
async function createSession() {
|
|
74
|
+
const response = await fetch(`/api/session/create`)
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(`Unexpected error`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { conversationId, token } = await response.json()
|
|
81
|
+
|
|
82
|
+
setConversationId(conversationId)
|
|
83
|
+
setToken(token)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleOnKeyDown(event) {
|
|
87
|
+
if (event.keyCode === 13) {
|
|
88
|
+
event.preventDefault()
|
|
89
|
+
|
|
90
|
+
interact()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
{conversationId && token ? (
|
|
97
|
+
<>
|
|
98
|
+
<div>
|
|
99
|
+
{messages.map(({ id, type, text }) => {
|
|
100
|
+
switch (type) {
|
|
101
|
+
case 'user':
|
|
102
|
+
return <div key={id}>user: {text}</div>
|
|
103
|
+
|
|
104
|
+
case 'bot':
|
|
105
|
+
return <div key={id}>bot: {text}</div>
|
|
106
|
+
}
|
|
107
|
+
})}
|
|
108
|
+
{thinking ? <div key="thinking">bot: thinking...</div> : null}
|
|
109
|
+
</div>
|
|
110
|
+
<AutoTextarea
|
|
111
|
+
value={text}
|
|
112
|
+
onChange={(e) => setText(event.target.value)}
|
|
113
|
+
onKeyDown={handleOnKeyDown}
|
|
114
|
+
/>
|
|
115
|
+
</>
|
|
116
|
+
) : (
|
|
117
|
+
<button onClick={() => createSession()}>Start Chat</button>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
You can find a wide-range of examples at [here](https://github.com/chatbotkit/node-sdk/tree/main/examples).
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
For detailed documentation on available types, please refer to the [type documentation](https://github.com/chatbotkit/node-sdk/blob/main/docs/react/modules.md).
|
|
131
|
+
|
|
132
|
+
Checkout the [ChatBotKit Documentation](https://chatbotkit.com/docs/react-sdk) for more information about the platform.
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
If you find a bug or would like to contribute to the ChatBotKit SDK, please open an issue or submit a pull request on the [official GitHub repository](https://github.com/chatbotkit/node-sdk).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {(event: React.ChangeEvent<HTMLTextAreaElement>) => any} onInputFn
|
|
9
|
+
*
|
|
10
|
+
* @param {{
|
|
11
|
+
* onInput?: onInputFn?,
|
|
12
|
+
* [name: string]: any
|
|
13
|
+
* }} [props]
|
|
14
|
+
*/
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
function AutoTextarea({ onInput, ...props } = {}) {
|
|
17
|
+
/**
|
|
18
|
+
* @param {React.ChangeEvent<HTMLTextAreaElement>} event
|
|
19
|
+
*/
|
|
20
|
+
function handleOnInput(event) {
|
|
21
|
+
const adjustment = `calc(${[event.target.style.paddingTop, event.target.style.paddingBottom]
|
|
22
|
+
.filter((f) => f)
|
|
23
|
+
.join(' + ') || '0px'})`;
|
|
24
|
+
event.target.style.height = 'auto';
|
|
25
|
+
event.target.style.height = `calc(${event.target.scrollHeight}px - ${adjustment})`;
|
|
26
|
+
if (onInput) {
|
|
27
|
+
onInput(event);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return react_1.default.createElement("textarea", { ...props, rows: 1, onInput: handleOnInput });
|
|
31
|
+
}
|
|
32
|
+
exports.default = AutoTextarea;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {(event: React.ChangeEvent<HTMLTextAreaElement>) => any} onInputFn
|
|
3
|
+
*
|
|
4
|
+
* @param {{
|
|
5
|
+
* onInput?: onInputFn?,
|
|
6
|
+
* [name: string]: any
|
|
7
|
+
* }} [props]
|
|
8
|
+
*/
|
|
9
|
+
export default function AutoTextarea({ onInput, ...props }?: {
|
|
10
|
+
[name: string]: any;
|
|
11
|
+
onInput?: onInputFn | null | undefined;
|
|
12
|
+
} | undefined): React.JSX.Element;
|
|
13
|
+
export type onInputFn = (event: React.ChangeEvent<HTMLTextAreaElement>) => any;
|
|
14
|
+
import React from 'react';
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
const index_js_1 = require("@chatbotkit/sdk/conversation/index.cjs");
|
|
5
|
+
const string_js_1 = require("../utils/string.cjs");
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {{
|
|
8
|
+
* id: string,
|
|
9
|
+
* type: string,
|
|
10
|
+
* text: string,
|
|
11
|
+
* createdAt: number,
|
|
12
|
+
* meta?: Record<string,any>
|
|
13
|
+
* }} Message
|
|
14
|
+
*
|
|
15
|
+
* @typedef {(error: any) => any} onErrorFn
|
|
16
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onSendFn
|
|
17
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onReceiveFn
|
|
18
|
+
*
|
|
19
|
+
* @param {{
|
|
20
|
+
* conversationId?: string,
|
|
21
|
+
* token?: string,
|
|
22
|
+
* messages?: Message[],
|
|
23
|
+
* parse?: boolean,
|
|
24
|
+
* stream?: boolean,
|
|
25
|
+
* verbose?: boolean,
|
|
26
|
+
* onError?: onErrorFn?,
|
|
27
|
+
* onSend?: onSendFn?,
|
|
28
|
+
* onReceive?: onReceiveFn?,
|
|
29
|
+
* }}[options]
|
|
30
|
+
*/
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
function useConversationManager({ conversationId: _conversationId = '', token: _token = '', messages: _messages = [], parse = false, stream = false, verbose = false, onError = null, onSend = null, onReceive = null, } = {}) {
|
|
33
|
+
// general states
|
|
34
|
+
const [conversationId, setConversationId] = (0, react_1.useState)(_conversationId);
|
|
35
|
+
const [token, setToken] = (0, react_1.useState)(_token);
|
|
36
|
+
const [messages, setMessages] = (0, react_1.useState)(_messages);
|
|
37
|
+
const [text, setText] = (0, react_1.useState)('');
|
|
38
|
+
const [entities, setEntities] = (0, react_1.useState)({});
|
|
39
|
+
const [outgoingMessage, setOutgoingMessage] = (0, react_1.useState)('');
|
|
40
|
+
// loading & thinking state
|
|
41
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
42
|
+
const [thinking, setThinking] = (0, react_1.useState)(false);
|
|
43
|
+
(0, react_1.useEffect)(() => {
|
|
44
|
+
// NOTE: if we are loading we must be thinking
|
|
45
|
+
if (loading) {
|
|
46
|
+
setThinking(true);
|
|
47
|
+
}
|
|
48
|
+
}, [loading]);
|
|
49
|
+
// entity methods
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} text
|
|
52
|
+
* @param {Record<string,string>} en
|
|
53
|
+
*/
|
|
54
|
+
function redactEnitities(text, en = entities) {
|
|
55
|
+
const steps = (0, string_js_1.replaceWithCoordinates)(text, Object.entries(en));
|
|
56
|
+
const output = steps.pop();
|
|
57
|
+
steps.forEach((step) => {
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
delete step.input;
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
delete step.output;
|
|
62
|
+
});
|
|
63
|
+
return [output, ...steps];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} text
|
|
67
|
+
* @param {Record<string,string>} en
|
|
68
|
+
*/
|
|
69
|
+
function unredactEnitities(text, en = entities) {
|
|
70
|
+
Object.entries(en).forEach(([real, redacted]) => {
|
|
71
|
+
text = text.split(redacted).join(real);
|
|
72
|
+
});
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
// state utility methods
|
|
76
|
+
/**
|
|
77
|
+
* @returns {Promise<void>}
|
|
78
|
+
*/
|
|
79
|
+
async function flushUserMessage() {
|
|
80
|
+
if (!text) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setText('');
|
|
84
|
+
setOutgoingMessage(text);
|
|
85
|
+
let newMessages = messages.slice(0);
|
|
86
|
+
newMessages = newMessages.concat({
|
|
87
|
+
id: (0, string_js_1.getRandomId)('message-'),
|
|
88
|
+
type: 'user',
|
|
89
|
+
text,
|
|
90
|
+
createdAt: Date.now(),
|
|
91
|
+
});
|
|
92
|
+
setMessages(newMessages);
|
|
93
|
+
}
|
|
94
|
+
// utility states
|
|
95
|
+
const [nextStep, setNextStep] = (0, react_1.useState)(null);
|
|
96
|
+
(0, react_1.useEffect)(() => {
|
|
97
|
+
if (!nextStep) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setNextStep(null);
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
switch (nextStep.fn) {
|
|
103
|
+
case 'continueConversation':
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
continueConversation(nextStep.options);
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
throw new Error(`Unrecognised fn: ${nextStep.fn}`);
|
|
110
|
+
}
|
|
111
|
+
}, [nextStep]);
|
|
112
|
+
// base methods
|
|
113
|
+
/**
|
|
114
|
+
* @param {{
|
|
115
|
+
* token?: string,
|
|
116
|
+
* }} [options]
|
|
117
|
+
* @returns {Promise<void>}
|
|
118
|
+
*/
|
|
119
|
+
async function continueConversation(options) {
|
|
120
|
+
let newMessages = messages.slice(0);
|
|
121
|
+
let thisText;
|
|
122
|
+
if (text) {
|
|
123
|
+
thisText = text;
|
|
124
|
+
const message = {
|
|
125
|
+
id: (0, string_js_1.getRandomId)('message-'),
|
|
126
|
+
text: text,
|
|
127
|
+
type: 'user',
|
|
128
|
+
createdAt: Date.now(),
|
|
129
|
+
};
|
|
130
|
+
newMessages = newMessages.concat([message]);
|
|
131
|
+
setText('');
|
|
132
|
+
}
|
|
133
|
+
else if (outgoingMessage) {
|
|
134
|
+
thisText = outgoingMessage;
|
|
135
|
+
setOutgoingMessage('');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setMessages(newMessages);
|
|
141
|
+
setLoading(true);
|
|
142
|
+
const [redactedText, ...redactedEntities] = redactEnitities(thisText, entities);
|
|
143
|
+
const secret = options?.token || token;
|
|
144
|
+
const client = new index_js_1.ConversationClient({ secret });
|
|
145
|
+
const completion = client.complete(conversationId, {
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
text: redactedText,
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
entities: redactedEntities,
|
|
150
|
+
parse: parse,
|
|
151
|
+
});
|
|
152
|
+
let iter;
|
|
153
|
+
if (stream) {
|
|
154
|
+
iter = completion.stream();
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
iter = (async function* () {
|
|
158
|
+
yield { type: 'receiveResult', data: await completion };
|
|
159
|
+
})();
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
if (onSend) {
|
|
163
|
+
await onSend(conversationId, newMessages, {});
|
|
164
|
+
}
|
|
165
|
+
const tempId = (0, string_js_1.getRandomId)('tmp-');
|
|
166
|
+
let tempText = '';
|
|
167
|
+
for await (let { type, data } of iter) {
|
|
168
|
+
switch (type) {
|
|
169
|
+
case 'intentDetectionEnd': {
|
|
170
|
+
if (!verbose) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const { action } = data;
|
|
174
|
+
if (!action) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
const { name, input } = action;
|
|
178
|
+
switch (name) {
|
|
179
|
+
case 'search': {
|
|
180
|
+
const id = (0, string_js_1.getRandomId)('tmp-');
|
|
181
|
+
newMessages = newMessages.concat([
|
|
182
|
+
{
|
|
183
|
+
id: id,
|
|
184
|
+
text: '',
|
|
185
|
+
type: 'context',
|
|
186
|
+
createdAt: Date.now(),
|
|
187
|
+
meta: {
|
|
188
|
+
search: input,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
setMessages(newMessages);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'token': {
|
|
199
|
+
if (!stream) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const { token } = data;
|
|
203
|
+
tempText += token;
|
|
204
|
+
setMessages([
|
|
205
|
+
...newMessages,
|
|
206
|
+
{
|
|
207
|
+
id: tempId,
|
|
208
|
+
text: tempText,
|
|
209
|
+
type: 'bot',
|
|
210
|
+
createdAt: Date.now(),
|
|
211
|
+
meta: {},
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
if (text.length) {
|
|
215
|
+
setThinking(false);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'sendResult': {
|
|
220
|
+
const { entities: newEntities } = data;
|
|
221
|
+
setEntities({ ...entities, ...newEntities });
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case 'receiveResult': {
|
|
225
|
+
const { id, text, parse } = data;
|
|
226
|
+
setMessages([
|
|
227
|
+
...newMessages,
|
|
228
|
+
{
|
|
229
|
+
id: id,
|
|
230
|
+
text: unredactEnitities((parse ? parse.stripped : text).trim(), entities),
|
|
231
|
+
type: 'bot',
|
|
232
|
+
createdAt: Date.now(),
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
setThinking(false);
|
|
236
|
+
if (onReceive) {
|
|
237
|
+
await onReceive(conversationId, newMessages, data);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
if (onError) {
|
|
246
|
+
onError(e);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
setLoading(false);
|
|
250
|
+
}
|
|
251
|
+
// helper methods
|
|
252
|
+
/**
|
|
253
|
+
* @param {{
|
|
254
|
+
* token?: string,
|
|
255
|
+
* conversationId?: string
|
|
256
|
+
* messages?: Message[]
|
|
257
|
+
* }} [options]
|
|
258
|
+
* @returns {void}
|
|
259
|
+
*/
|
|
260
|
+
function interact(options) {
|
|
261
|
+
if (options?.token) {
|
|
262
|
+
setToken(options.token);
|
|
263
|
+
}
|
|
264
|
+
if (options?.conversationId) {
|
|
265
|
+
setConversationId(options.conversationId);
|
|
266
|
+
}
|
|
267
|
+
if (options?.messages) {
|
|
268
|
+
setMessages(options.messages);
|
|
269
|
+
}
|
|
270
|
+
if (options?.conversationId || conversationId) {
|
|
271
|
+
setNextStep({
|
|
272
|
+
// @ts-ignore
|
|
273
|
+
fn: 'continueConversation',
|
|
274
|
+
options,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
throw new Error(`No conversation id specified`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* @returns {void}
|
|
283
|
+
*/
|
|
284
|
+
function reset() {
|
|
285
|
+
setMessages([]);
|
|
286
|
+
setConversationId('');
|
|
287
|
+
}
|
|
288
|
+
// final
|
|
289
|
+
return {
|
|
290
|
+
text,
|
|
291
|
+
setText,
|
|
292
|
+
token,
|
|
293
|
+
setToken,
|
|
294
|
+
conversationId,
|
|
295
|
+
setConversationId,
|
|
296
|
+
messages,
|
|
297
|
+
setMessages,
|
|
298
|
+
flushUserMessage,
|
|
299
|
+
continueConversation,
|
|
300
|
+
interact,
|
|
301
|
+
reset,
|
|
302
|
+
loading,
|
|
303
|
+
thinking,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
exports.default = useConversationManager;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* id: string,
|
|
4
|
+
* type: string,
|
|
5
|
+
* text: string,
|
|
6
|
+
* createdAt: number,
|
|
7
|
+
* meta?: Record<string,any>
|
|
8
|
+
* }} Message
|
|
9
|
+
*
|
|
10
|
+
* @typedef {(error: any) => any} onErrorFn
|
|
11
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onSendFn
|
|
12
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onReceiveFn
|
|
13
|
+
*
|
|
14
|
+
* @param {{
|
|
15
|
+
* conversationId?: string,
|
|
16
|
+
* token?: string,
|
|
17
|
+
* messages?: Message[],
|
|
18
|
+
* parse?: boolean,
|
|
19
|
+
* stream?: boolean,
|
|
20
|
+
* verbose?: boolean,
|
|
21
|
+
* onError?: onErrorFn?,
|
|
22
|
+
* onSend?: onSendFn?,
|
|
23
|
+
* onReceive?: onReceiveFn?,
|
|
24
|
+
* }}[options]
|
|
25
|
+
*/
|
|
26
|
+
export default function useConversationManager({ conversationId: _conversationId, token: _token, messages: _messages, parse, stream, verbose, onError, onSend, onReceive, }?: {
|
|
27
|
+
conversationId?: string | undefined;
|
|
28
|
+
token?: string | undefined;
|
|
29
|
+
messages?: Message[] | undefined;
|
|
30
|
+
parse?: boolean | undefined;
|
|
31
|
+
stream?: boolean | undefined;
|
|
32
|
+
verbose?: boolean | undefined;
|
|
33
|
+
onError?: onErrorFn | null | undefined;
|
|
34
|
+
onSend?: onSendFn | null | undefined;
|
|
35
|
+
onReceive?: onReceiveFn | null | undefined;
|
|
36
|
+
} | undefined): {
|
|
37
|
+
text: string;
|
|
38
|
+
setText: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
39
|
+
token: string;
|
|
40
|
+
setToken: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
41
|
+
conversationId: string;
|
|
42
|
+
setConversationId: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
43
|
+
messages: Message[];
|
|
44
|
+
setMessages: import("react").Dispatch<import("react").SetStateAction<Message[]>>;
|
|
45
|
+
flushUserMessage: () => Promise<void>;
|
|
46
|
+
continueConversation: (options?: {
|
|
47
|
+
token?: string | undefined;
|
|
48
|
+
} | undefined) => Promise<void>;
|
|
49
|
+
interact: (options?: {
|
|
50
|
+
token?: string | undefined;
|
|
51
|
+
conversationId?: string | undefined;
|
|
52
|
+
messages?: Message[] | undefined;
|
|
53
|
+
} | undefined) => void;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
loading: boolean;
|
|
56
|
+
thinking: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type Message = {
|
|
59
|
+
id: string;
|
|
60
|
+
type: string;
|
|
61
|
+
text: string;
|
|
62
|
+
createdAt: number;
|
|
63
|
+
meta?: Record<string, any>;
|
|
64
|
+
};
|
|
65
|
+
export type onErrorFn = (error: any) => any;
|
|
66
|
+
export type onSendFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
|
|
67
|
+
export type onReceiveFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.useConversationManager = exports.AutoTextarea = void 0;
|
|
7
|
+
var AutoTextarea_js_1 = require("./components/AutoTextarea.cjs");
|
|
8
|
+
Object.defineProperty(exports, "AutoTextarea", { enumerable: true, get: function () { return __importDefault(AutoTextarea_js_1).default; } });
|
|
9
|
+
var useConversationManager_js_1 = require("./hooks/useConversationManager.cjs");
|
|
10
|
+
Object.defineProperty(exports, "useConversationManager", { enumerable: true, get: function () { return __importDefault(useConversationManager_js_1).default; } });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.replaceWithCoordinates = exports.replaceBetween = exports.getRandomId = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} [prefix]
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
function getRandomId(prefix) {
|
|
9
|
+
return `${prefix || ''}${Math.random().toString(32).slice(2)}`;
|
|
10
|
+
}
|
|
11
|
+
exports.getRandomId = getRandomId;
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} input
|
|
14
|
+
* @param {number} begin
|
|
15
|
+
* @param {number} end
|
|
16
|
+
* @param {string} replacement
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function replaceBetween(input, begin, end, replacement) {
|
|
20
|
+
return input.substring(0, begin) + replacement + input.substring(end);
|
|
21
|
+
}
|
|
22
|
+
exports.replaceBetween = replaceBetween;
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} input
|
|
25
|
+
* @param {[string, string][]} replacements
|
|
26
|
+
* @returns {({begin: number, end: number, input: string, output: string}|string)[]}
|
|
27
|
+
*/
|
|
28
|
+
function replaceWithCoordinates(input, replacements) {
|
|
29
|
+
const output = [];
|
|
30
|
+
let currentIndex = 0;
|
|
31
|
+
while (currentIndex < input.length) {
|
|
32
|
+
let found = false;
|
|
33
|
+
for (const [search, replacement] of replacements) {
|
|
34
|
+
if (input.startsWith(search, currentIndex)) {
|
|
35
|
+
const origInput = input;
|
|
36
|
+
const resultOutput = replaceBetween(input, currentIndex, currentIndex + search.length, replacement);
|
|
37
|
+
output.push({
|
|
38
|
+
begin: currentIndex,
|
|
39
|
+
end: currentIndex + replacement.length,
|
|
40
|
+
input: origInput,
|
|
41
|
+
output: resultOutput,
|
|
42
|
+
});
|
|
43
|
+
input = resultOutput;
|
|
44
|
+
currentIndex += replacement.length;
|
|
45
|
+
found = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!found) {
|
|
50
|
+
currentIndex += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
output.push(input);
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
exports.replaceWithCoordinates = replaceWithCoordinates;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} [prefix]
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
export function getRandomId(prefix?: string | undefined): string;
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} input
|
|
8
|
+
* @param {number} begin
|
|
9
|
+
* @param {number} end
|
|
10
|
+
* @param {string} replacement
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function replaceBetween(input: string, begin: number, end: number, replacement: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} input
|
|
16
|
+
* @param {[string, string][]} replacements
|
|
17
|
+
* @returns {({begin: number, end: number, input: string, output: string}|string)[]}
|
|
18
|
+
*/
|
|
19
|
+
export function replaceWithCoordinates(input: string, replacements: [string, string][]): (string | {
|
|
20
|
+
begin: number;
|
|
21
|
+
end: number;
|
|
22
|
+
input: string;
|
|
23
|
+
output: string;
|
|
24
|
+
})[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {(event: React.ChangeEvent<HTMLTextAreaElement>) => any} onInputFn
|
|
3
|
+
*
|
|
4
|
+
* @param {{
|
|
5
|
+
* onInput?: onInputFn?,
|
|
6
|
+
* [name: string]: any
|
|
7
|
+
* }} [props]
|
|
8
|
+
*/
|
|
9
|
+
export default function AutoTextarea({ onInput, ...props }?: {
|
|
10
|
+
[name: string]: any;
|
|
11
|
+
onInput?: onInputFn | null | undefined;
|
|
12
|
+
} | undefined): React.JSX.Element;
|
|
13
|
+
export type onInputFn = (event: React.ChangeEvent<HTMLTextAreaElement>) => any;
|
|
14
|
+
import React from 'react';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {(event: React.ChangeEvent<HTMLTextAreaElement>) => any} onInputFn
|
|
4
|
+
*
|
|
5
|
+
* @param {{
|
|
6
|
+
* onInput?: onInputFn?,
|
|
7
|
+
* [name: string]: any
|
|
8
|
+
* }} [props]
|
|
9
|
+
*/
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
export default function AutoTextarea({ onInput, ...props } = {}) {
|
|
12
|
+
/**
|
|
13
|
+
* @param {React.ChangeEvent<HTMLTextAreaElement>} event
|
|
14
|
+
*/
|
|
15
|
+
function handleOnInput(event) {
|
|
16
|
+
const adjustment = `calc(${[event.target.style.paddingTop, event.target.style.paddingBottom]
|
|
17
|
+
.filter((f) => f)
|
|
18
|
+
.join(' + ') || '0px'})`;
|
|
19
|
+
event.target.style.height = 'auto';
|
|
20
|
+
event.target.style.height = `calc(${event.target.scrollHeight}px - ${adjustment})`;
|
|
21
|
+
if (onInput) {
|
|
22
|
+
onInput(event);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return React.createElement("textarea", { ...props, rows: 1, onInput: handleOnInput });
|
|
26
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* id: string,
|
|
4
|
+
* type: string,
|
|
5
|
+
* text: string,
|
|
6
|
+
* createdAt: number,
|
|
7
|
+
* meta?: Record<string,any>
|
|
8
|
+
* }} Message
|
|
9
|
+
*
|
|
10
|
+
* @typedef {(error: any) => any} onErrorFn
|
|
11
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onSendFn
|
|
12
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onReceiveFn
|
|
13
|
+
*
|
|
14
|
+
* @param {{
|
|
15
|
+
* conversationId?: string,
|
|
16
|
+
* token?: string,
|
|
17
|
+
* messages?: Message[],
|
|
18
|
+
* parse?: boolean,
|
|
19
|
+
* stream?: boolean,
|
|
20
|
+
* verbose?: boolean,
|
|
21
|
+
* onError?: onErrorFn?,
|
|
22
|
+
* onSend?: onSendFn?,
|
|
23
|
+
* onReceive?: onReceiveFn?,
|
|
24
|
+
* }}[options]
|
|
25
|
+
*/
|
|
26
|
+
export default function useConversationManager({ conversationId: _conversationId, token: _token, messages: _messages, parse, stream, verbose, onError, onSend, onReceive, }?: {
|
|
27
|
+
conversationId?: string | undefined;
|
|
28
|
+
token?: string | undefined;
|
|
29
|
+
messages?: Message[] | undefined;
|
|
30
|
+
parse?: boolean | undefined;
|
|
31
|
+
stream?: boolean | undefined;
|
|
32
|
+
verbose?: boolean | undefined;
|
|
33
|
+
onError?: onErrorFn | null | undefined;
|
|
34
|
+
onSend?: onSendFn | null | undefined;
|
|
35
|
+
onReceive?: onReceiveFn | null | undefined;
|
|
36
|
+
} | undefined): {
|
|
37
|
+
text: string;
|
|
38
|
+
setText: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
39
|
+
token: string;
|
|
40
|
+
setToken: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
41
|
+
conversationId: string;
|
|
42
|
+
setConversationId: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
43
|
+
messages: Message[];
|
|
44
|
+
setMessages: import("react").Dispatch<import("react").SetStateAction<Message[]>>;
|
|
45
|
+
flushUserMessage: () => Promise<void>;
|
|
46
|
+
continueConversation: (options?: {
|
|
47
|
+
token?: string | undefined;
|
|
48
|
+
} | undefined) => Promise<void>;
|
|
49
|
+
interact: (options?: {
|
|
50
|
+
token?: string | undefined;
|
|
51
|
+
conversationId?: string | undefined;
|
|
52
|
+
messages?: Message[] | undefined;
|
|
53
|
+
} | undefined) => void;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
loading: boolean;
|
|
56
|
+
thinking: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type Message = {
|
|
59
|
+
id: string;
|
|
60
|
+
type: string;
|
|
61
|
+
text: string;
|
|
62
|
+
createdAt: number;
|
|
63
|
+
meta?: Record<string, any>;
|
|
64
|
+
};
|
|
65
|
+
export type onErrorFn = (error: any) => any;
|
|
66
|
+
export type onSendFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
|
|
67
|
+
export type onReceiveFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { ConversationClient } from '@chatbotkit/sdk/conversation/index.js';
|
|
3
|
+
import { getRandomId, replaceWithCoordinates } from '../utils/string.js';
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* id: string,
|
|
7
|
+
* type: string,
|
|
8
|
+
* text: string,
|
|
9
|
+
* createdAt: number,
|
|
10
|
+
* meta?: Record<string,any>
|
|
11
|
+
* }} Message
|
|
12
|
+
*
|
|
13
|
+
* @typedef {(error: any) => any} onErrorFn
|
|
14
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onSendFn
|
|
15
|
+
* @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onReceiveFn
|
|
16
|
+
*
|
|
17
|
+
* @param {{
|
|
18
|
+
* conversationId?: string,
|
|
19
|
+
* token?: string,
|
|
20
|
+
* messages?: Message[],
|
|
21
|
+
* parse?: boolean,
|
|
22
|
+
* stream?: boolean,
|
|
23
|
+
* verbose?: boolean,
|
|
24
|
+
* onError?: onErrorFn?,
|
|
25
|
+
* onSend?: onSendFn?,
|
|
26
|
+
* onReceive?: onReceiveFn?,
|
|
27
|
+
* }}[options]
|
|
28
|
+
*/
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
export default function useConversationManager({ conversationId: _conversationId = '', token: _token = '', messages: _messages = [], parse = false, stream = false, verbose = false, onError = null, onSend = null, onReceive = null, } = {}) {
|
|
31
|
+
// general states
|
|
32
|
+
const [conversationId, setConversationId] = useState(_conversationId);
|
|
33
|
+
const [token, setToken] = useState(_token);
|
|
34
|
+
const [messages, setMessages] = useState(_messages);
|
|
35
|
+
const [text, setText] = useState('');
|
|
36
|
+
const [entities, setEntities] = useState({});
|
|
37
|
+
const [outgoingMessage, setOutgoingMessage] = useState('');
|
|
38
|
+
// loading & thinking state
|
|
39
|
+
const [loading, setLoading] = useState(false);
|
|
40
|
+
const [thinking, setThinking] = useState(false);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
// NOTE: if we are loading we must be thinking
|
|
43
|
+
if (loading) {
|
|
44
|
+
setThinking(true);
|
|
45
|
+
}
|
|
46
|
+
}, [loading]);
|
|
47
|
+
// entity methods
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} text
|
|
50
|
+
* @param {Record<string,string>} en
|
|
51
|
+
*/
|
|
52
|
+
function redactEnitities(text, en = entities) {
|
|
53
|
+
const steps = replaceWithCoordinates(text, Object.entries(en));
|
|
54
|
+
const output = steps.pop();
|
|
55
|
+
steps.forEach((step) => {
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
delete step.input;
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
delete step.output;
|
|
60
|
+
});
|
|
61
|
+
return [output, ...steps];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} text
|
|
65
|
+
* @param {Record<string,string>} en
|
|
66
|
+
*/
|
|
67
|
+
function unredactEnitities(text, en = entities) {
|
|
68
|
+
Object.entries(en).forEach(([real, redacted]) => {
|
|
69
|
+
text = text.split(redacted).join(real);
|
|
70
|
+
});
|
|
71
|
+
return text;
|
|
72
|
+
}
|
|
73
|
+
// state utility methods
|
|
74
|
+
/**
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
async function flushUserMessage() {
|
|
78
|
+
if (!text) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setText('');
|
|
82
|
+
setOutgoingMessage(text);
|
|
83
|
+
let newMessages = messages.slice(0);
|
|
84
|
+
newMessages = newMessages.concat({
|
|
85
|
+
id: getRandomId('message-'),
|
|
86
|
+
type: 'user',
|
|
87
|
+
text,
|
|
88
|
+
createdAt: Date.now(),
|
|
89
|
+
});
|
|
90
|
+
setMessages(newMessages);
|
|
91
|
+
}
|
|
92
|
+
// utility states
|
|
93
|
+
const [nextStep, setNextStep] = useState(null);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!nextStep) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setNextStep(null);
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
switch (nextStep.fn) {
|
|
101
|
+
case 'continueConversation':
|
|
102
|
+
// @ts-ignore
|
|
103
|
+
continueConversation(nextStep.options);
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
throw new Error(`Unrecognised fn: ${nextStep.fn}`);
|
|
108
|
+
}
|
|
109
|
+
}, [nextStep]);
|
|
110
|
+
// base methods
|
|
111
|
+
/**
|
|
112
|
+
* @param {{
|
|
113
|
+
* token?: string,
|
|
114
|
+
* }} [options]
|
|
115
|
+
* @returns {Promise<void>}
|
|
116
|
+
*/
|
|
117
|
+
async function continueConversation(options) {
|
|
118
|
+
let newMessages = messages.slice(0);
|
|
119
|
+
let thisText;
|
|
120
|
+
if (text) {
|
|
121
|
+
thisText = text;
|
|
122
|
+
const message = {
|
|
123
|
+
id: getRandomId('message-'),
|
|
124
|
+
text: text,
|
|
125
|
+
type: 'user',
|
|
126
|
+
createdAt: Date.now(),
|
|
127
|
+
};
|
|
128
|
+
newMessages = newMessages.concat([message]);
|
|
129
|
+
setText('');
|
|
130
|
+
}
|
|
131
|
+
else if (outgoingMessage) {
|
|
132
|
+
thisText = outgoingMessage;
|
|
133
|
+
setOutgoingMessage('');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
setMessages(newMessages);
|
|
139
|
+
setLoading(true);
|
|
140
|
+
const [redactedText, ...redactedEntities] = redactEnitities(thisText, entities);
|
|
141
|
+
const secret = options?.token || token;
|
|
142
|
+
const client = new ConversationClient({ secret });
|
|
143
|
+
const completion = client.complete(conversationId, {
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
text: redactedText,
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
entities: redactedEntities,
|
|
148
|
+
parse: parse,
|
|
149
|
+
});
|
|
150
|
+
let iter;
|
|
151
|
+
if (stream) {
|
|
152
|
+
iter = completion.stream();
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
iter = (async function* () {
|
|
156
|
+
yield { type: 'receiveResult', data: await completion };
|
|
157
|
+
})();
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
if (onSend) {
|
|
161
|
+
await onSend(conversationId, newMessages, {});
|
|
162
|
+
}
|
|
163
|
+
const tempId = getRandomId('tmp-');
|
|
164
|
+
let tempText = '';
|
|
165
|
+
for await (let { type, data } of iter) {
|
|
166
|
+
switch (type) {
|
|
167
|
+
case 'intentDetectionEnd': {
|
|
168
|
+
if (!verbose) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
const { action } = data;
|
|
172
|
+
if (!action) {
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
const { name, input } = action;
|
|
176
|
+
switch (name) {
|
|
177
|
+
case 'search': {
|
|
178
|
+
const id = getRandomId('tmp-');
|
|
179
|
+
newMessages = newMessages.concat([
|
|
180
|
+
{
|
|
181
|
+
id: id,
|
|
182
|
+
text: '',
|
|
183
|
+
type: 'context',
|
|
184
|
+
createdAt: Date.now(),
|
|
185
|
+
meta: {
|
|
186
|
+
search: input,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
setMessages(newMessages);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case 'token': {
|
|
197
|
+
if (!stream) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
const { token } = data;
|
|
201
|
+
tempText += token;
|
|
202
|
+
setMessages([
|
|
203
|
+
...newMessages,
|
|
204
|
+
{
|
|
205
|
+
id: tempId,
|
|
206
|
+
text: tempText,
|
|
207
|
+
type: 'bot',
|
|
208
|
+
createdAt: Date.now(),
|
|
209
|
+
meta: {},
|
|
210
|
+
},
|
|
211
|
+
]);
|
|
212
|
+
if (text.length) {
|
|
213
|
+
setThinking(false);
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'sendResult': {
|
|
218
|
+
const { entities: newEntities } = data;
|
|
219
|
+
setEntities({ ...entities, ...newEntities });
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case 'receiveResult': {
|
|
223
|
+
const { id, text, parse } = data;
|
|
224
|
+
setMessages([
|
|
225
|
+
...newMessages,
|
|
226
|
+
{
|
|
227
|
+
id: id,
|
|
228
|
+
text: unredactEnitities((parse ? parse.stripped : text).trim(), entities),
|
|
229
|
+
type: 'bot',
|
|
230
|
+
createdAt: Date.now(),
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
setThinking(false);
|
|
234
|
+
if (onReceive) {
|
|
235
|
+
await onReceive(conversationId, newMessages, data);
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
if (onError) {
|
|
244
|
+
onError(e);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
setLoading(false);
|
|
248
|
+
}
|
|
249
|
+
// helper methods
|
|
250
|
+
/**
|
|
251
|
+
* @param {{
|
|
252
|
+
* token?: string,
|
|
253
|
+
* conversationId?: string
|
|
254
|
+
* messages?: Message[]
|
|
255
|
+
* }} [options]
|
|
256
|
+
* @returns {void}
|
|
257
|
+
*/
|
|
258
|
+
function interact(options) {
|
|
259
|
+
if (options?.token) {
|
|
260
|
+
setToken(options.token);
|
|
261
|
+
}
|
|
262
|
+
if (options?.conversationId) {
|
|
263
|
+
setConversationId(options.conversationId);
|
|
264
|
+
}
|
|
265
|
+
if (options?.messages) {
|
|
266
|
+
setMessages(options.messages);
|
|
267
|
+
}
|
|
268
|
+
if (options?.conversationId || conversationId) {
|
|
269
|
+
setNextStep({
|
|
270
|
+
// @ts-ignore
|
|
271
|
+
fn: 'continueConversation',
|
|
272
|
+
options,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
throw new Error(`No conversation id specified`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* @returns {void}
|
|
281
|
+
*/
|
|
282
|
+
function reset() {
|
|
283
|
+
setMessages([]);
|
|
284
|
+
setConversationId('');
|
|
285
|
+
}
|
|
286
|
+
// final
|
|
287
|
+
return {
|
|
288
|
+
text,
|
|
289
|
+
setText,
|
|
290
|
+
token,
|
|
291
|
+
setToken,
|
|
292
|
+
conversationId,
|
|
293
|
+
setConversationId,
|
|
294
|
+
messages,
|
|
295
|
+
setMessages,
|
|
296
|
+
flushUserMessage,
|
|
297
|
+
continueConversation,
|
|
298
|
+
interact,
|
|
299
|
+
reset,
|
|
300
|
+
loading,
|
|
301
|
+
thinking,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} [prefix]
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
export function getRandomId(prefix?: string | undefined): string;
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} input
|
|
8
|
+
* @param {number} begin
|
|
9
|
+
* @param {number} end
|
|
10
|
+
* @param {string} replacement
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function replaceBetween(input: string, begin: number, end: number, replacement: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} input
|
|
16
|
+
* @param {[string, string][]} replacements
|
|
17
|
+
* @returns {({begin: number, end: number, input: string, output: string}|string)[]}
|
|
18
|
+
*/
|
|
19
|
+
export function replaceWithCoordinates(input: string, replacements: [string, string][]): (string | {
|
|
20
|
+
begin: number;
|
|
21
|
+
end: number;
|
|
22
|
+
input: string;
|
|
23
|
+
output: string;
|
|
24
|
+
})[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} [prefix]
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
export function getRandomId(prefix) {
|
|
6
|
+
return `${prefix || ''}${Math.random().toString(32).slice(2)}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} input
|
|
10
|
+
* @param {number} begin
|
|
11
|
+
* @param {number} end
|
|
12
|
+
* @param {string} replacement
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function replaceBetween(input, begin, end, replacement) {
|
|
16
|
+
return input.substring(0, begin) + replacement + input.substring(end);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} input
|
|
20
|
+
* @param {[string, string][]} replacements
|
|
21
|
+
* @returns {({begin: number, end: number, input: string, output: string}|string)[]}
|
|
22
|
+
*/
|
|
23
|
+
export function replaceWithCoordinates(input, replacements) {
|
|
24
|
+
const output = [];
|
|
25
|
+
let currentIndex = 0;
|
|
26
|
+
while (currentIndex < input.length) {
|
|
27
|
+
let found = false;
|
|
28
|
+
for (const [search, replacement] of replacements) {
|
|
29
|
+
if (input.startsWith(search, currentIndex)) {
|
|
30
|
+
const origInput = input;
|
|
31
|
+
const resultOutput = replaceBetween(input, currentIndex, currentIndex + search.length, replacement);
|
|
32
|
+
output.push({
|
|
33
|
+
begin: currentIndex,
|
|
34
|
+
end: currentIndex + replacement.length,
|
|
35
|
+
input: origInput,
|
|
36
|
+
output: resultOutput,
|
|
37
|
+
});
|
|
38
|
+
input = resultOutput;
|
|
39
|
+
currentIndex += replacement.length;
|
|
40
|
+
found = true;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!found) {
|
|
45
|
+
currentIndex += 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
output.push(input);
|
|
49
|
+
return output;
|
|
50
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chatbotkit/react",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The fastest way to build advanced AI chat bots",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/chatbotkit/node-sdk.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/chatbotkit/node-sdk/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/chatbotkit/node-sdk#readme",
|
|
14
|
+
"author": "",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"gpt3",
|
|
17
|
+
"gpt4",
|
|
18
|
+
"chat",
|
|
19
|
+
"chatbot",
|
|
20
|
+
"chatbotkit",
|
|
21
|
+
"react"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/esm/index.js",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/cjs/index.d.ts",
|
|
29
|
+
"default": "./dist/cjs/index.cjs"
|
|
30
|
+
},
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/esm/index.d.ts",
|
|
33
|
+
"default": "./dist/esm/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"./components/AutoTextarea.js": {
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./dist/cjs/components/AutoTextarea.d.ts",
|
|
39
|
+
"default": "./dist/cjs/components/AutoTextarea.cjs"
|
|
40
|
+
},
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/esm/components/AutoTextarea.d.ts",
|
|
43
|
+
"default": "./dist/esm/components/AutoTextarea.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"./hooks/useConversationManager.js": {
|
|
47
|
+
"require": {
|
|
48
|
+
"types": "./dist/cjs/hooks/useConversationManager.d.ts",
|
|
49
|
+
"default": "./dist/cjs/hooks/useConversationManager.cjs"
|
|
50
|
+
},
|
|
51
|
+
"import": {
|
|
52
|
+
"types": "./dist/esm/hooks/useConversationManager.d.ts",
|
|
53
|
+
"default": "./dist/esm/hooks/useConversationManager.js"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"./index.js": {
|
|
57
|
+
"require": {
|
|
58
|
+
"types": "./dist/cjs/index.d.ts",
|
|
59
|
+
"default": "./dist/cjs/index.cjs"
|
|
60
|
+
},
|
|
61
|
+
"import": {
|
|
62
|
+
"types": "./dist/esm/index.d.ts",
|
|
63
|
+
"default": "./dist/esm/index.js"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"./utils/string.js": {
|
|
67
|
+
"require": {
|
|
68
|
+
"types": "./dist/cjs/utils/string.d.ts",
|
|
69
|
+
"default": "./dist/cjs/utils/string.cjs"
|
|
70
|
+
},
|
|
71
|
+
"import": {
|
|
72
|
+
"types": "./dist/esm/utils/string.d.ts",
|
|
73
|
+
"default": "./dist/esm/utils/string.js"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "run-s build:*",
|
|
79
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
80
|
+
"build:cjs_rename": "find ./dist/cjs -name '*.js' -exec rename .js .cjs {} +",
|
|
81
|
+
"build:cjs_rewrite_cjs": "find ./dist/cjs -name '*.cjs' -exec sed -i 's/require(\"\\(.*\\)\\.js\")/require(\"\\1.cjs\")/g' {} +",
|
|
82
|
+
"build:cjs_rewrite_ts": "find ./dist/cjs -name '*.ts' -exec sed -i 's/from \"\\(.*\\)\\.js\"/from \"\\1.cjs\"/g' {} +",
|
|
83
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
84
|
+
"build:exports": "node scripts/build-exports.js",
|
|
85
|
+
"test": "true"
|
|
86
|
+
},
|
|
87
|
+
"types": "./dist/esm/index.d.ts",
|
|
88
|
+
"dependencies": {
|
|
89
|
+
"@chatbotkit/sdk": "*"
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@tsconfig/recommended": "^1.0.2",
|
|
93
|
+
"@types/react": "^18.2.6",
|
|
94
|
+
"npm-run-all": "^4.1.5",
|
|
95
|
+
"typescript": "^5.0.4"
|
|
96
|
+
}
|
|
97
|
+
}
|