@chatbotkit/react 1.5.3 → 1.7.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.
@@ -0,0 +1,107 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { isValidElement } from 'react';
3
+ import { stream } from '../utils/stream.js';
4
+ async function* complete({ client, messages, functions, maxRecusion = 3, ...options }) {
5
+ if (maxRecusion <= 0) {
6
+ return;
7
+ }
8
+ messages = messages.slice(0);
9
+ const it = client
10
+ .complete(null, {
11
+ ...options,
12
+ messages: messages.map(({ type, text, meta }) => {
13
+ return {
14
+ type,
15
+ text,
16
+ meta,
17
+ };
18
+ }),
19
+ functions: functions?.map(({ name, description, parameters }) => {
20
+ return {
21
+ name,
22
+ description,
23
+ parameters,
24
+ };
25
+ }),
26
+ })
27
+ .stream();
28
+ for await (const item of it) {
29
+ yield item;
30
+ const { type, data } = item;
31
+ if (type === 'message') {
32
+ const message = data;
33
+ messages.push(message);
34
+ if (message.meta?.activity?.type === 'request') {
35
+ const name = message.meta.activity.function?.name;
36
+ const args = message.meta.activity.function?.arguments;
37
+ const fn = functions?.find((fn) => fn.name === name);
38
+ if (fn && typeof fn.handler === 'function') {
39
+ const output = await fn.handler(args);
40
+ let text;
41
+ let children;
42
+ let result;
43
+ if (typeof output === 'string') {
44
+ text = undefined;
45
+ children = undefined;
46
+ result = output;
47
+ }
48
+ else if (isValidElement(output)) {
49
+ text = '';
50
+ children = output;
51
+ result = undefined;
52
+ }
53
+ else {
54
+ if (typeof output?.text === 'string') {
55
+ text = output.text;
56
+ }
57
+ if (isValidElement(output?.children)) {
58
+ children = output.children;
59
+ }
60
+ if (output?.result) {
61
+ result = output.result;
62
+ }
63
+ }
64
+ if (text || children) {
65
+ yield {
66
+ type: 'message',
67
+ data: {
68
+ type: 'bot',
69
+ text: text ? text : '',
70
+ children: children ? _jsx(_Fragment, { children: children }) : undefined,
71
+ },
72
+ };
73
+ }
74
+ if (result) {
75
+ const activityMessage = {
76
+ type: 'activity',
77
+ text: '',
78
+ meta: {
79
+ activity: {
80
+ type: 'response',
81
+ function: {
82
+ name,
83
+ arguments: args,
84
+ result: JSON.stringify(result),
85
+ },
86
+ },
87
+ },
88
+ };
89
+ yield { type: 'message', data: activityMessage };
90
+ messages.push(activityMessage);
91
+ yield* complete({
92
+ ...options,
93
+ client,
94
+ messages,
95
+ functions,
96
+ maxRecusion: maxRecusion - 1,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ export function streamComplete(options) {
105
+ return stream(complete(options));
106
+ }
107
+ export default complete;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useRef, useEffect } from 'react';
2
+ import React, { useEffect, useRef } from 'react';
3
3
  function AutoScrollAnchor() {
4
4
  return _jsx("div", { className: "auto-scroll-anchor", style: { height: '1px' } });
5
5
  }
@@ -1,11 +1,4 @@
1
1
  export function AutoTextarea(props?: {
2
2
  [name: string]: any;
3
- onInput?: onInputFn | null | undefined;
4
- onFocus?: onFocusFn | null | undefined;
5
- adjustOnInput?: boolean | null | undefined;
6
- adjustOnFocus?: boolean | null | undefined;
7
3
  } | undefined): import("react/jsx-runtime").JSX.Element;
8
4
  export default AutoTextarea;
9
- export type onInputFn = (event: React.ChangeEvent<HTMLTextAreaElement>) => any;
10
- export type onFocusFn = (event: React.ChangeEvent<HTMLTextAreaElement>) => any;
11
- import React from 'react';
@@ -1,30 +1,34 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import React, { useEffect, useRef } from 'react';
3
3
  export function AutoTextarea(props) {
4
- const { onInput, onFocus, adjustOnInput = true, adjustOnFocus = false, ...rest } = props || {};
5
- function handleEvent(event) {
6
- const adjustment = `calc(${[event.target.style.paddingTop, event.target.style.paddingBottom]
4
+ const ref = useRef(null);
5
+ function recalibrate(textarea) {
6
+ const adjustment = `calc(${[textarea.style.paddingTop, textarea.style.paddingBottom]
7
7
  .filter((f) => f)
8
8
  .join(' + ') || '0px'})`;
9
- event.target.style.height = 'auto';
10
- event.target.style.height = `calc(${event.target.scrollHeight}px - ${adjustment})`;
9
+ textarea.style.height = 'auto';
10
+ textarea.style.height = `calc(${textarea.scrollHeight}px - ${adjustment})`;
11
11
  }
12
- function handleOnInput(event) {
13
- if (adjustOnInput) {
14
- handleEvent(event);
12
+ useEffect(() => {
13
+ const textarea = ref.current;
14
+ if (textarea) {
15
+ recalibrate(textarea);
16
+ const observer = new MutationObserver((mutationsList) => {
17
+ for (const mutation of mutationsList) {
18
+ if (mutation.type === 'childList' ||
19
+ mutation.type === 'characterData') {
20
+ recalibrate(textarea);
21
+ }
22
+ }
23
+ });
24
+ observer.observe(textarea, {
25
+ childList: true,
26
+ subtree: true,
27
+ characterData: true,
28
+ });
29
+ return () => observer.disconnect();
15
30
  }
16
- if (onInput) {
17
- onInput(event);
18
- }
19
- }
20
- function handleOnFocus(event) {
21
- if (adjustOnFocus) {
22
- handleEvent(event);
23
- }
24
- if (onFocus) {
25
- onFocus(event);
26
- }
27
- }
28
- return (_jsx("textarea", { ...rest, rows: 1, onInput: handleOnInput, onFocus: handleOnFocus }));
31
+ }, []);
32
+ return _jsx("textarea", { ref: ref, rows: 1, ...props });
29
33
  }
30
34
  export default AutoTextarea;
@@ -0,0 +1,5 @@
1
+ export function ConversationManager({ children, ...options }: import('../hooks/useConversationManager.js').UseConversationManagerOptions & {
2
+ children: import('react').ReactNode;
3
+ }): import('react').ReactElement;
4
+ export const ConversationContext: import("react").Context<import("../hooks/useConversationManager.js").UseConversationManagerResult>;
5
+ export default ConversationManager;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext } from 'react';
3
+ import useConversationManager from '../hooks/useConversationManager.js';
4
+ export const ConversationContext = createContext(({
5
+ token: undefined,
6
+ setToken: () => { },
7
+ conversationId: undefined,
8
+ setConversationId: () => { },
9
+ botId: undefined,
10
+ setBotId: () => { },
11
+ backstory: undefined,
12
+ setBackstory: () => { },
13
+ model: undefined,
14
+ setModel: () => { },
15
+ datasetId: undefined,
16
+ setDatasetId: () => { },
17
+ skillsetId: undefined,
18
+ setSkillsetId: () => { },
19
+ text: '',
20
+ setText: () => { },
21
+ messages: [],
22
+ setMessages: () => { },
23
+ thinking: false,
24
+ setThinking: () => { },
25
+ typing: false,
26
+ setTyping: () => { },
27
+ error: null,
28
+ setError: () => { },
29
+ submit: () => { },
30
+ trigger: () => { },
31
+ }));
32
+ export function ConversationManager({ children, ...options }) {
33
+ const manager = useConversationManager(options);
34
+ return (_jsx(ConversationContext.Provider, { value: manager, children: children }));
35
+ }
36
+ export default ConversationManager;
@@ -1,38 +1,4 @@
1
- export function useConversationManager(options: {
2
- [key: string]: any;
3
- client?: ConversationClient | undefined;
4
- endpoint?: string | undefined;
5
- token?: string | 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: any;
19
- setModel: import("react").Dispatch<any>;
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>>;
24
- text: string;
25
- setText: import("react").Dispatch<import("react").SetStateAction<string>>;
26
- messages: Message[];
27
- setMessages: import("react").Dispatch<import("react").SetStateAction<Message[]>>;
28
- thinking: boolean;
29
- setThinking: import("react").Dispatch<import("react").SetStateAction<boolean>>;
30
- typing: boolean;
31
- setTyping: import("react").Dispatch<import("react").SetStateAction<boolean>>;
32
- error: any;
33
- setError: import("react").Dispatch<any>;
34
- submit: () => Promise<void>;
35
- };
1
+ export function useConversationManager(options: UseConversationManagerOptions): UseConversationManagerResult;
36
2
  export default useConversationManager;
37
3
  export type ModelConfig = {
38
4
  maxTokens?: number;
@@ -48,8 +14,50 @@ export type Model = string | {
48
14
  config?: ModelConfig;
49
15
  };
50
16
  export type Message = {
51
- id: string;
52
- type: 'bot' | 'user';
17
+ id?: string;
18
+ type: 'bot' | 'user' | 'context' | 'instruction' | 'backstory' | 'activity';
19
+ text: string;
20
+ meta?: Record<string, any>;
21
+ };
22
+ export type EndpointURL = string;
23
+ export type EndpointFunction = (conversationId: any, request: any) => AsyncGenerator<any>;
24
+ export type UseConversationManagerOptions = {
25
+ [key: string]: any;
26
+ client?: ConversationClient | undefined;
27
+ endpoint?: string | EndpointFunction | undefined;
28
+ token?: string | undefined;
29
+ conversationId?: string | undefined;
30
+ backstory?: string | undefined;
31
+ Model?: string | undefined;
32
+ datasetId?: string | undefined;
33
+ skillsetId?: string | undefined;
34
+ };
35
+ export type UseConversationManagerResult = {
36
+ token?: string | undefined;
37
+ setToken: (token: string) => void;
38
+ conversationId?: string | undefined;
39
+ setConversationId: (conversationId: string) => void;
40
+ botId?: string | undefined;
41
+ setBotId: (botId: string) => void;
42
+ backstory?: string | undefined;
43
+ setBackstory: (backstory: string) => void;
44
+ model?: Model | undefined;
45
+ setModel: (model: Model) => void;
46
+ datasetId?: string | undefined;
47
+ setDatasetId: (datasetId: string) => void;
48
+ skillsetId?: string | undefined;
49
+ setSkillsetId: (skillsetId: string) => void;
53
50
  text: string;
51
+ setText: (text: string) => void;
52
+ messages: Message[];
53
+ setMessages: (messages: Message[]) => void;
54
+ thinking: boolean;
55
+ setThinking: (thinking: boolean) => void;
56
+ typing: boolean;
57
+ setTyping: (typing: boolean) => void;
58
+ error: any;
59
+ setError: (error: any) => void;
60
+ submit: () => void;
61
+ trigger: (name: string, ...args: any) => void;
54
62
  };
55
63
  import { ConversationClient } from '@chatbotkit/sdk';
@@ -1,20 +1,33 @@
1
1
  import { useMemo, useState } from 'react';
2
- import { ConversationClient } from '@chatbotkit/sdk';
3
- import { getRandomId } from '../utils/string.js';
4
2
  import { cloneAndExtend } from '../utils/object.js';
3
+ import { consume } from '../utils/stream.js';
4
+ import { getRandomId } from '../utils/string.js';
5
+ import { ConversationClient } from '@chatbotkit/sdk';
5
6
  export function useConversationManager(options) {
6
- const { client: _client, endpoint, token: _token, conversationId: _conversationId, backstory: _backstory, model: _model, datasetId: _datasetId, skillsetId: _skillsetId, ...rest } = options;
7
+ const { client: _client, endpoint, token: _token, conversationId: _conversationId, botId: _botId, backstory: _backstory, model: _model, datasetId: _datasetId, skillsetId: _skillsetId, ...rest } = options;
7
8
  const [token, setToken] = useState(_token);
8
9
  const [conversationId, setConversationId] = useState(_conversationId);
10
+ const [botId, setBotId] = useState(_botId);
9
11
  const [backstory, setBackstory] = useState(_backstory);
10
12
  const [model, setModel] = useState(_model);
11
13
  const [datasetId, setDatasetId] = useState(_datasetId);
12
14
  const [skillsetId, setSkillsetId] = useState(_skillsetId);
13
15
  const client = useMemo(() => {
16
+ if (typeof endpoint === 'function') {
17
+ return {
18
+ complete(conversationId, options) {
19
+ return {
20
+ async *stream() {
21
+ yield* consume(endpoint(conversationId, options));
22
+ },
23
+ };
24
+ },
25
+ };
26
+ }
14
27
  const options = { ...rest, secret: token || '' };
15
28
  let thisClient = _client || new ConversationClient(options);
16
29
  const extension = {};
17
- if (endpoint) {
30
+ if (typeof endpoint === 'string') {
18
31
  extension.url = new URL(globalThis.window?.location?.origin || 'about:blank');
19
32
  extension.endpoints = {
20
33
  '/api/v1/conversation/complete': endpoint,
@@ -26,26 +39,16 @@ export function useConversationManager(options) {
26
39
  if (Object.keys(extension).length === 0) {
27
40
  return thisClient;
28
41
  }
29
- return cloneAndExtend(thisClient, extension);
42
+ else {
43
+ return cloneAndExtend(thisClient, extension);
44
+ }
30
45
  }, [_client, endpoint, token]);
31
46
  const [text, setText] = useState((''));
32
47
  const [messages, setMessages] = useState(([]));
33
48
  const [thinking, setThinking] = useState(false);
34
49
  const [typing, setTyping] = useState(false);
35
50
  const [error, setError] = useState((null));
36
- async function submit() {
37
- if (!text) {
38
- return;
39
- }
40
- setText('');
41
- const userMessage = {
42
- id: getRandomId('message-'),
43
- type: 'user',
44
- text: text,
45
- };
46
- let newMessages = messages.slice(0);
47
- newMessages = [...newMessages, userMessage];
48
- setMessages([...newMessages]);
51
+ async function stream(newMessages) {
49
52
  setThinking(true);
50
53
  let it;
51
54
  try {
@@ -58,7 +61,9 @@ export function useConversationManager(options) {
58
61
  model: model,
59
62
  datasetId: datasetId,
60
63
  skillsetId: skillsetId,
61
- messages: newMessages.slice(-100),
64
+ messages: newMessages
65
+ .slice(-100)
66
+ .map(({ type, text, meta }) => ({ type, text, meta })),
62
67
  });
63
68
  }
64
69
  }
@@ -75,16 +80,37 @@ export function useConversationManager(options) {
75
80
  let alreadyStreaming = false;
76
81
  try {
77
82
  for await (const event of it.stream()) {
78
- if (event.type === 'token') {
79
- if (!alreadyStreaming) {
80
- alreadyStreaming = true;
81
- newMessages = [...newMessages, botMessage];
82
- setMessages(newMessages);
83
+ switch (event.type) {
84
+ case 'token': {
85
+ if (!alreadyStreaming) {
86
+ alreadyStreaming = true;
87
+ newMessages = [...newMessages, botMessage];
88
+ setMessages(newMessages);
89
+ setThinking(false);
90
+ setTyping(true);
91
+ }
92
+ botMessage.text += event.data.token;
93
+ setMessages([...newMessages]);
94
+ break;
95
+ }
96
+ case 'message': {
97
+ const message = event.data;
98
+ if (botMessage.text !== message.text ||
99
+ message.type === 'activity' ||
100
+ typeof message.children !== 'undefined') {
101
+ const newMessage = {
102
+ id: getRandomId('message-'),
103
+ ...event.data,
104
+ };
105
+ newMessages = [...newMessages, newMessage];
106
+ setMessages([...newMessages]);
107
+ }
108
+ break;
109
+ }
110
+ case 'result': {
83
111
  setThinking(false);
84
- setTyping(true);
112
+ setTyping(false);
85
113
  }
86
- botMessage.text += event.data.token;
87
- setMessages([...newMessages]);
88
114
  }
89
115
  }
90
116
  }
@@ -95,11 +121,47 @@ export function useConversationManager(options) {
95
121
  setTyping(false);
96
122
  }
97
123
  }
124
+ async function submit() {
125
+ if (!text) {
126
+ return;
127
+ }
128
+ setText('');
129
+ const userMessage = {
130
+ id: getRandomId('message-'),
131
+ type: 'user',
132
+ text: text,
133
+ };
134
+ let newMessages = messages.slice(0);
135
+ newMessages = [...newMessages, userMessage];
136
+ setMessages([...newMessages]);
137
+ await stream(newMessages);
138
+ }
139
+ async function trigger(name, ...args) {
140
+ const newMessages = [
141
+ ...messages,
142
+ ({
143
+ type: 'activity',
144
+ text: '',
145
+ meta: {
146
+ activity: {
147
+ type: 'trigger',
148
+ function: {
149
+ name: name,
150
+ arguments: args,
151
+ },
152
+ },
153
+ },
154
+ }),
155
+ ];
156
+ await stream(newMessages);
157
+ }
98
158
  return {
99
159
  token,
100
160
  setToken,
101
161
  conversationId,
102
162
  setConversationId,
163
+ botId,
164
+ setBotId,
103
165
  backstory,
104
166
  setBackstory,
105
167
  model,
@@ -119,6 +181,7 @@ export function useConversationManager(options) {
119
181
  error,
120
182
  setError,
121
183
  submit,
184
+ trigger,
122
185
  };
123
186
  }
124
187
  export default useConversationManager;
@@ -3,3 +3,4 @@ export { ChatMessage } from "./components/ChatMessage.js";
3
3
  export { ChatMessages } from "./components/ChatMessages.js";
4
4
  export { AutoTextarea } from "./components/AutoTextarea.js";
5
5
  export { useConversationManager } from "./hooks/useConversationManager.js";
6
+ export { ConversationManager, ConversationContext } from "./components/ConversationManager.js";
package/dist/esm/index.js CHANGED
@@ -2,4 +2,5 @@ export { ChatInput } from './components/ChatInput.js';
2
2
  export { ChatMessage } from './components/ChatMessage.js';
3
3
  export { ChatMessages } from './components/ChatMessages.js';
4
4
  export { AutoTextarea } from './components/AutoTextarea.js';
5
+ export { ConversationManager, ConversationContext, } from './components/ConversationManager.js';
5
6
  export { useConversationManager } from './hooks/useConversationManager.js';
@@ -0,0 +1,6 @@
1
+ export function stream(source: StreamSource): StreamResult;
2
+ export function consume(source: ConsumeSource): ConsumeResult;
3
+ export type StreamSource = any;
4
+ export type StreamResult = any;
5
+ export type ConsumeSource = any;
6
+ export type ConsumeResult = any;
@@ -0,0 +1,42 @@
1
+ export function stream(source) {
2
+ let it;
3
+ if ('next' in source && typeof source.next === 'function') {
4
+ it = source.next();
5
+ }
6
+ else if ('stream' in source && typeof source.stream === 'function') {
7
+ return stream(source.stream());
8
+ }
9
+ else {
10
+ throw new Error('Invalid source');
11
+ }
12
+ return new Promise((resolve, reject) => {
13
+ it.then((res) => {
14
+ if (res.done) {
15
+ resolve({ iteratorResult: res });
16
+ }
17
+ else {
18
+ resolve({ iteratorResult: res, next: stream(source) });
19
+ }
20
+ });
21
+ it.catch((error) => reject(error));
22
+ });
23
+ }
24
+ export function consume(source) {
25
+ return {
26
+ [Symbol.asyncIterator]: function () {
27
+ return {
28
+ current: source,
29
+ async next() {
30
+ const { iteratorResult, next } = await this.current;
31
+ if (next) {
32
+ this.current = next;
33
+ }
34
+ else {
35
+ iteratorResult.done = true;
36
+ }
37
+ return iteratorResult;
38
+ },
39
+ };
40
+ },
41
+ };
42
+ }