- useAiLib => AiLib management - useAiLibState => AiLib state management - ChatBox => AI chatbox component
npm install @connexup/ai-reactjavascript
import React, { useEffect, useState } from 'react';
import { AiLibOptions, RequestOptions } from '@connexup/ai-api';
import { useAiLibState } from './useAiLibState';
import { useAiLib } from './useAiLib';
import { v4 as uuid } from 'uuid';type Props = {
options: AiLibOptions & RequestOptions;
connectOptions: RequestOptions;
renderMessage?: (message: any) => React.ReactNode;
};
export function ChatBox({
options,
connectOptions,
renderMessage,
}: Props) {
const aiLib = useAiLib(options);
const { status, streamMessage, fullMessages, error } = useAiLibState({
aiLib,
selector: (state) => state,
});
const [list, setList] = useState<
{
sender: string;
msg: string;
error?: string | null;
status: 'complete' | 'waiting';
}[]
>([]);
const [inputVal, setInputVal] = useState('');
const [streamContent, setStreamContent] = useState('');
useEffect(() => {
if (status === 'open') {
setStreamContent((pre) => {
if (!streamMessage) return pre;
let content: any;
if (streamMessage.type === 'agent_response') {
content = streamMessage.content;
} else if (streamMessage.type === 'menu_table') {
content = streamMessage.table_config;
} else if (streamMessage.type === 'menu_preview') {
content = streamMessage.message.preview_config;
}
if (typeof content === 'object' && content !== null) {
return pre + JSON.stringify(content);
}
return pre + (content || '');
});
} else if (status === 'closed') {
setList((pre) => {
const res = [...pre];
const lastItem = res[res.length - 1];
if (lastItem?.status === 'waiting') {
res.splice(res.length - 1, 1, {
...lastItem,
msg: streamContent || '好的,停下来了,有问题再问我!',
status: 'complete',
});
}
console.log('closed:', streamContent, pre, res);
return res;
});
setStreamContent('');
} else if (status === 'error') {
setList((pre) => {
const res = [...pre];
const lastItem = res[res.length - 1];
if (lastItem?.status === 'waiting') {
res.splice(res.length - 1, 1, {
...lastItem,
msg: '哦哦,我出错了,请稍后再试!',
error: error ? error.errorCode + ', ' + error.errorMessage : null,
});
}
return res;
});
}
}, [status, streamMessage, error]);
const handleChat = (value: string) => {
aiLib.disconnect();
setList((pre) => {
const res = [...pre];
const lastItem = res[res.length - 1];
if (lastItem?.status === 'waiting') {
res.splice(res.length - 1, 1, {
...lastItem,
msg: streamContent || '好的,停下来了,有问题再问我!',
status: 'complete',
});
}
return [
...res,
{ sender: 'user', msg: value, status: 'complete' },
{ sender: 'ai', msg: '', status: 'waiting' },
];
});
setStreamContent('');
aiLib.connect({
...connectOptions,
data: connectOptions.data
? JSON.stringify(connectOptions.data)
: undefined,
});
};
// const handleChatNoStream = () => {
// aiLib.disconnect();
// setList((pre) => {
// const res = [...pre];
// const lastItem = res[res.length - 1];
// if (lastItem?.status === 'waiting') {
// res.splice(res.length - 1, 1, {
// ...lastItem,
// msg: streamContent || '好的,停下来了,有问题再问我!',
// status: 'complete',
// });
// }
// return [
// ...res,
// { sender: 'user', msg: '测试非推流接口', status: 'complete' },
// { sender: 'ai', msg: '', status: 'waiting' },
// ];
// });
// setStreamContent('');
// aiLib.connect({
// url: 'http://localhost:3030/test-typeorm',
// method: 'GET',
// streaming: false,
// });
// };
const handleStopChat = () => {
aiLib.disconnect();
};
return (
list:
style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
borderBottom: '1px solid gray',
height: '300px',
overflow: 'auto',
}}
>
{list.map((item, index) =>
renderMessage ? (
renderMessage(item)
) : (
style={{
width: '30%',
background: item.sender === 'ai' ? 'white' : 'lightblue',
alignSelf: item.sender === 'ai' ? 'flex-start' : 'flex-end',
textAlign: item.sender === 'ai' ? 'left' : 'right',
color: 'black',
wordBreak: 'break-all',
}}
key={index}
>
{item.status === 'complete'
? item.msg
: streamContent || 'waiting...'}
{item.error}
)
)}
``