@chatbotkit/react 0.8.0 → 1.1.5

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.
@@ -1,62 +1,51 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useRef, useEffect } from 'react';
3
- export function AutoScrollAnchor() {
4
- return React.createElement("div", { className: "auto-scroll-anchor ![height:1px]" });
3
+ function AutoScrollAnchor() {
4
+ return _jsx("div", { className: "auto-scroll-anchor", style: { height: '1px' } });
5
5
  }
6
- /**
7
- * @param {{
8
- * anchor?: 'top'|'bottom',
9
- * childList?: boolean,
10
- * subtree?: boolean,
11
- * block?: 'start'|'end',
12
- * delay?: number,
13
- * [name: string]: any
14
- * }} props
15
- */
16
- export default function AutoScroller({ anchor = 'bottom', childList = true, subtree = false, block = 'end', delay = 3000, disabled, children, ...props }) {
17
- const rootRef = useRef();
6
+ export function AutoScroller(props) {
7
+ const { anchor = 'bottom', childList = true, subtree = false, block = 'end', delay = 3000, disabled, children, ...rest } = props || {};
8
+ const rootRef = (useRef());
18
9
  useEffect(() => {
19
10
  if (disabled) {
20
11
  return;
21
12
  }
22
13
  let visible = false;
23
14
  let pause = false;
24
- let timeout = 0;
15
+ let timeout;
25
16
  const io = new IntersectionObserver((entries) => {
26
17
  if (pause) {
27
18
  return;
28
19
  }
29
20
  visible = entries.some((entry) => entry.isIntersecting);
30
21
  });
31
- // @ts-ignore
32
- io.observe(rootRef.current?.querySelector('.auto-scroll-anchor'));
22
+ const anchor = rootRef.current?.querySelector('.auto-scroll-anchor');
23
+ if (anchor) {
24
+ io.observe(anchor);
25
+ }
33
26
  const mo = new MutationObserver(() => {
34
27
  if (!visible) {
35
28
  return;
36
29
  }
37
30
  pause = true;
38
31
  rootRef.current
39
- // @ts-ignore
40
32
  ?.querySelector('.auto-scroll-anchor')
41
33
  ?.scrollIntoView({ behavior: 'smooth', block });
42
34
  clearTimeout(timeout);
43
- // @ts-ignore
44
35
  timeout = setTimeout(() => {
45
36
  visible = true;
46
37
  pause = false;
47
38
  }, delay);
48
39
  });
49
- // @ts-ignore
50
- mo.observe(rootRef.current, { childList, subtree });
40
+ const root = rootRef.current;
41
+ if (root) {
42
+ mo.observe(root, { childList, subtree });
43
+ }
51
44
  return () => {
52
45
  io.disconnect();
53
46
  mo.disconnect();
54
47
  };
55
48
  }, [disabled]);
56
- return (
57
- // @ts-ignore
58
- React.createElement("div", { ref: rootRef, ...props },
59
- anchor === 'top' ? React.createElement(AutoScrollAnchor, { key: "top" }) : null,
60
- children,
61
- anchor === 'bottom' ? React.createElement(AutoScrollAnchor, { key: "bottom" }) : null));
49
+ return (_jsxs("div", { ref: rootRef, ...rest, children: [anchor === 'top' ? _jsx(AutoScrollAnchor, {}, "top") : null, children, anchor === 'bottom' ? _jsx(AutoScrollAnchor, {}, "bottom") : null] }));
62
50
  }
51
+ export default AutoScroller;
@@ -1,14 +1,7 @@
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 }?: {
1
+ export function AutoTextarea(props?: {
10
2
  [name: string]: any;
11
3
  onInput?: onInputFn | null | undefined;
12
- } | undefined): React.JSX.Element;
4
+ } | undefined): import("react/jsx-runtime").JSX.Element;
5
+ export default AutoTextarea;
13
6
  export type onInputFn = (event: React.ChangeEvent<HTMLTextAreaElement>) => any;
14
7
  import React from 'react';
@@ -1,18 +1,7 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- /**
4
- * @typedef {(event: React.ChangeEvent<HTMLTextAreaElement>) => any} onInputFn
5
- *
6
- * @param {{
7
- * onInput?: onInputFn?,
8
- * [name: string]: any
9
- * }} [props]
10
- */
11
- // @ts-ignore
12
- export default function AutoTextarea({ onInput, ...props } = {}) {
13
- /**
14
- * @param {React.ChangeEvent<HTMLTextAreaElement>} event
15
- */
3
+ export function AutoTextarea(props) {
4
+ const { onInput, ...rest } = props || {};
16
5
  function handleOnInput(event) {
17
6
  const adjustment = `calc(${[event.target.style.paddingTop, event.target.style.paddingBottom]
18
7
  .filter((f) => f)
@@ -23,5 +12,6 @@ export default function AutoTextarea({ onInput, ...props } = {}) {
23
12
  onInput(event);
24
13
  }
25
14
  }
26
- return React.createElement("textarea", { ...props, rows: 1, onInput: handleOnInput });
15
+ return _jsx("textarea", { ...rest, rows: 1, onInput: handleOnInput });
27
16
  }
17
+ export default AutoTextarea;
@@ -1,65 +1,41 @@
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
- * stream?: boolean,
19
- * verbose?: boolean,
20
- * onError?: onErrorFn?,
21
- * onSend?: onSendFn?,
22
- * onReceive?: onReceiveFn?,
23
- * }}[options]
24
- */
25
- export default function useConversationManager({ conversationId: _conversationId, token: _token, messages: _messages, stream, verbose, onError, onSend, onReceive, }?: {
26
- conversationId?: string | undefined;
1
+ export function useConversationManager(options: {
2
+ [key: string]: any;
3
+ client?: ConversationClient | undefined;
4
+ endpoint?: string | undefined;
27
5
  token?: string | undefined;
28
- messages?: Message[] | undefined;
29
- stream?: boolean | undefined;
30
- verbose?: boolean | undefined;
31
- onError?: onErrorFn | null | undefined;
32
- onSend?: onSendFn | null | undefined;
33
- onReceive?: onReceiveFn | null | undefined;
34
- } | undefined): {
6
+ conversationId?: string | undefined;
7
+ backstory?: string | undefined;
8
+ model?: string | undefined;
9
+ datasetId?: string | undefined;
10
+ skillsetId?: string | undefined;
11
+ }): {
12
+ token: string | undefined;
13
+ setToken: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
14
+ conversationId: string | undefined;
15
+ setConversationId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
16
+ backstory: string | undefined;
17
+ setBackstory: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
18
+ model: string | undefined;
19
+ setModel: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
20
+ datasetId: string | undefined;
21
+ setDatasetId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
22
+ skillsetId: string | undefined;
23
+ setSkillsetId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
35
24
  text: string;
36
25
  setText: import("react").Dispatch<import("react").SetStateAction<string>>;
37
- token: string;
38
- setToken: import("react").Dispatch<import("react").SetStateAction<string>>;
39
- conversationId: string;
40
- setConversationId: import("react").Dispatch<import("react").SetStateAction<string>>;
41
26
  messages: Message[];
42
27
  setMessages: import("react").Dispatch<import("react").SetStateAction<Message[]>>;
43
- flushUserMessage: () => Promise<void>;
44
- continueConversation: (options?: {
45
- token?: string | undefined;
46
- } | undefined) => Promise<void>;
47
- interact: (options?: {
48
- token?: string | undefined;
49
- conversationId?: string | undefined;
50
- messages?: Message[] | undefined;
51
- } | undefined) => void;
52
- reset: () => void;
53
- loading: boolean;
54
28
  thinking: boolean;
29
+ setThinking: import("react").Dispatch<import("react").SetStateAction<boolean>>;
30
+ writing: boolean;
31
+ setWriting: import("react").Dispatch<import("react").SetStateAction<boolean>>;
32
+ error: any;
33
+ setError: import("react").Dispatch<any>;
34
+ submit: () => Promise<void>;
55
35
  };
36
+ export default useConversationManager;
56
37
  export type Message = {
57
- id: string;
58
- type: string;
38
+ type: 'bot' | 'user';
59
39
  text: string;
60
- createdAt: number;
61
- meta?: Record<string, any>;
62
40
  };
63
- export type onErrorFn = (error: any) => any;
64
- export type onSendFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
65
- export type onReceiveFn = (conversationId: string, messages: Message[], data: Record<string, any>) => any;
41
+ import { ConversationClient } from '@chatbotkit/sdk';
@@ -1,301 +1,121 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
2
- import { useState, useEffect } from 'react';
3
- import { ConversationClient } from '@chatbotkit/sdk/conversation/index.js';
4
- import { getRandomId, replaceWithCoordinates } from '../utils/string.js';
5
- /**
6
- * @typedef {{
7
- * id: string,
8
- * type: string,
9
- * text: string,
10
- * createdAt: number,
11
- * meta?: Record<string,any>
12
- * }} Message
13
- *
14
- * @typedef {(error: any) => any} onErrorFn
15
- * @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onSendFn
16
- * @typedef {(conversationId: string, messages: Message[], data: Record<string,any>) => any} onReceiveFn
17
- *
18
- * @param {{
19
- * conversationId?: string,
20
- * token?: string,
21
- * messages?: Message[],
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 = [], stream = false, verbose = false, onError = null, onSend = null, onReceive = null, } = {}) {
31
- // general states
32
- const [conversationId, setConversationId] = useState(_conversationId);
1
+ import { useMemo, useState } from 'react';
2
+ import { ConversationClient } from '@chatbotkit/sdk';
3
+ import { cloneAndExtend } from '../utils/object.js';
4
+ export function useConversationManager(options) {
5
+ const { client: _client, endpoint, token: _token, conversationId: _conversationId, backstory: _backstory, model: _model, datasetId: _datasetId, skillsetId: _skillsetId, ...rest } = options;
33
6
  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);
7
+ const [conversationId, setConversationId] = useState(_conversationId);
8
+ const [backstory, setBackstory] = useState(_backstory);
9
+ const [model, setModel] = useState(_model);
10
+ const [datasetId, setDatasetId] = useState(_datasetId);
11
+ const [skillsetId, setSkillsetId] = useState(_skillsetId);
12
+ const client = useMemo(() => {
13
+ const options = { ...rest, secret: token || '' };
14
+ let thisClient = _client || new ConversationClient(options);
15
+ const extension = {};
16
+ if (endpoint) {
17
+ extension.url = new URL(globalThis.window?.location?.origin || 'about:blank');
18
+ extension.endpoints = {
19
+ '/api/v1/conversation/complete': endpoint,
20
+ };
45
21
  }
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() {
22
+ if (token) {
23
+ extension.secret = token;
24
+ }
25
+ if (Object.keys(extension).length === 0) {
26
+ return thisClient;
27
+ }
28
+ return cloneAndExtend(thisClient, extension);
29
+ }, [_client, endpoint, token]);
30
+ const [text, setText] = useState((''));
31
+ const [messages, setMessages] = useState(([]));
32
+ const [thinking, setThinking] = useState(false);
33
+ const [writing, setWriting] = useState(false);
34
+ const [error, setError] = useState((null));
35
+ async function submit() {
78
36
  if (!text) {
79
37
  return;
80
38
  }
81
39
  setText('');
82
- setOutgoingMessage(text);
83
- let newMessages = messages.slice(0);
84
- newMessages = newMessages.concat({
85
- id: getRandomId('message-'),
40
+ const userMessage = {
86
41
  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) {
42
+ text: text,
43
+ };
118
44
  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('');
45
+ newMessages = [...newMessages, userMessage];
46
+ setMessages([...newMessages]);
47
+ setThinking(true);
48
+ let it;
49
+ try {
50
+ if (conversationId) {
51
+ it = client.complete(conversationId, { text });
52
+ }
53
+ else {
54
+ it = client.complete(null, {
55
+ backstory: backstory,
56
+ model: model,
57
+ datasetId: datasetId,
58
+ skillsetId: skillsetId,
59
+ messages: newMessages.slice(-100),
60
+ });
61
+ }
134
62
  }
135
- else {
63
+ catch (e) {
64
+ setThinking(false);
65
+ setError(e);
136
66
  return;
137
67
  }
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
- // @ts-ignore
144
- const completion = client.complete(conversationId, {
145
- text: redactedText,
146
- entities: redactedEntities,
147
- });
148
- let iter;
149
- if (stream) {
150
- iter = completion.stream();
151
- }
152
- else {
153
- iter = (async function* () {
154
- yield { type: 'receiveResult', data: await completion };
155
- })();
156
- }
68
+ const botMessage = {
69
+ type: 'bot',
70
+ text: '',
71
+ };
72
+ let alreadyStreaming = false;
157
73
  try {
158
- if (onSend) {
159
- await onSend(conversationId, newMessages, {});
160
- }
161
- const tempId = getRandomId('tmp-');
162
- let tempText = '';
163
- for await (let { type, data } of iter) {
164
- switch (type) {
165
- case 'intentDetectionEnd': {
166
- if (!verbose) {
167
- break;
168
- }
169
- const { action } = data;
170
- if (!action) {
171
- break;
172
- }
173
- const { name, input } = action;
174
- switch (name) {
175
- case 'search': {
176
- const id = getRandomId('tmp-');
177
- newMessages = newMessages.concat([
178
- {
179
- id: id,
180
- text: '',
181
- type: 'context',
182
- createdAt: Date.now(),
183
- meta: {
184
- search: input,
185
- },
186
- },
187
- ]);
188
- setMessages(newMessages);
189
- break;
190
- }
191
- }
192
- break;
193
- }
194
- case 'token': {
195
- if (!stream) {
196
- break;
197
- }
198
- const { token } = data;
199
- tempText += token;
200
- setMessages([
201
- ...newMessages,
202
- {
203
- id: tempId,
204
- text: tempText,
205
- type: 'bot',
206
- createdAt: Date.now(),
207
- meta: {},
208
- },
209
- ]);
210
- if (tempText.length) {
211
- setThinking(false);
212
- }
213
- break;
214
- }
215
- case 'sendResult': {
216
- const { entities: newEntities } = data;
217
- setEntities({ ...entities, ...newEntities });
218
- break;
219
- }
220
- case 'receiveResult': {
221
- const { id, text, parse } = data;
222
- setMessages([
223
- ...newMessages,
224
- {
225
- id: id,
226
- text: unredactEnitities((parse ? parse.stripped : text).trim(), entities),
227
- type: 'bot',
228
- createdAt: Date.now(),
229
- },
230
- ]);
74
+ for await (const event of it.stream()) {
75
+ if (event.type === 'token') {
76
+ if (!alreadyStreaming) {
77
+ alreadyStreaming = true;
78
+ newMessages = [...newMessages, botMessage];
79
+ setMessages(newMessages);
231
80
  setThinking(false);
232
- if (onReceive) {
233
- await onReceive(conversationId, newMessages, data);
234
- }
235
- break;
81
+ setWriting(true);
236
82
  }
83
+ botMessage.text += event.data.token;
84
+ setMessages([...newMessages]);
237
85
  }
238
86
  }
239
87
  }
240
88
  catch (e) {
241
- if (onError) {
242
- onError(e);
243
- }
244
- }
245
- setLoading(false);
246
- }
247
- // helper methods
248
- /**
249
- * @param {{
250
- * token?: string,
251
- * conversationId?: string
252
- * messages?: Message[]
253
- * }} [options]
254
- * @returns {void}
255
- */
256
- function interact(options) {
257
- if (options?.token) {
258
- setToken(options.token);
259
- }
260
- if (options?.conversationId) {
261
- setConversationId(options.conversationId);
262
- }
263
- if (options?.messages) {
264
- setMessages(options.messages);
89
+ setError(e);
265
90
  }
266
- if (options?.conversationId || conversationId) {
267
- setNextStep({
268
- // @ts-ignore
269
- fn: 'continueConversation',
270
- options,
271
- });
91
+ finally {
92
+ setWriting(false);
272
93
  }
273
- else {
274
- throw new Error(`No conversation id specified`);
275
- }
276
- }
277
- /**
278
- * @returns {void}
279
- */
280
- function reset() {
281
- setMessages([]);
282
- setConversationId('');
283
94
  }
284
- // final
285
95
  return {
286
- text,
287
- setText,
288
96
  token,
289
97
  setToken,
290
98
  conversationId,
291
99
  setConversationId,
100
+ backstory,
101
+ setBackstory,
102
+ model,
103
+ setModel,
104
+ datasetId,
105
+ setDatasetId,
106
+ skillsetId,
107
+ setSkillsetId,
108
+ text,
109
+ setText,
292
110
  messages,
293
111
  setMessages,
294
- flushUserMessage,
295
- continueConversation,
296
- interact,
297
- reset,
298
- loading,
299
112
  thinking,
113
+ setThinking,
114
+ writing,
115
+ setWriting,
116
+ error,
117
+ setError,
118
+ submit,
300
119
  };
301
120
  }
121
+ export default useConversationManager;
@@ -1,2 +1,2 @@
1
- export { default as AutoTextarea } from "./components/AutoTextarea.js";
2
- export { default as useConversationManager } from "./hooks/useConversationManager.js";
1
+ export { AutoTextarea } from "./components/AutoTextarea.js";
2
+ export { useConversationManager } from "./hooks/useConversationManager.js";
package/dist/esm/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { default as AutoTextarea } from './components/AutoTextarea.js';
2
- export { default as useConversationManager } from './hooks/useConversationManager.js';
1
+ export { AutoTextarea } from './components/AutoTextarea.js';
2
+ export { useConversationManager } from './hooks/useConversationManager.js';
@@ -0,0 +1 @@
1
+ export function cloneAndExtend<T>(object: T, extension: object): T;
@@ -0,0 +1,3 @@
1
+ export function cloneAndExtend(object, extension) {
2
+ return Object.assign(Object.assign(Object.create(Object.getPrototypeOf(object)), object), extension);
3
+ }
@@ -1,21 +1,5 @@
1
- /**
2
- * @param {string} [prefix]
3
- * @returns {string}
4
- */
5
1
  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
2
  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
3
  export function replaceWithCoordinates(input: string, replacements: [string, string][]): (string | {
20
4
  begin: number;
21
5
  end: number;