@charisma-ai/react
Version:
Charisma.ai chat component for React
261 lines (260 loc) • 7.45 kB
JavaScript
import React, { useEffect, useRef, useCallback, useReducer, useState, createContext, useContext } from "react";
import { useQueuedConversation } from "./QueuedConversation.js";
import { usePlaythroughContext } from "./Playthrough.js";
export var ChatMode = /*#__PURE__*/ function(ChatMode) {
ChatMode["Tap"] = "tap";
ChatMode["Chat"] = "chat";
return ChatMode;
}({});
const reducer = (prevState, action)=>{
switch(action.type){
case "MESSAGE_CHARACTER":
{
return {
...prevState,
messages: [
...prevState.messages,
action.payload
],
mode: action.payload.tapToContinue ? "tap" : "chat"
};
}
case "MESSAGE_PLAYER":
{
return {
...prevState,
messages: [
...prevState.messages,
action.payload
],
inputValue: ""
};
}
case "START_TYPING":
{
return {
...prevState,
isTyping: true
};
}
case "STOP_TYPING":
{
return {
...prevState,
isTyping: false
};
}
case "TYPE":
{
return {
...prevState,
inputValue: action.payload
};
}
case "RESET":
{
return {
...prevState,
messages: []
};
}
case "RESTART":
{
const index = prevState.messages.findIndex((message)=>message.type === "character" || message.type === "media" || message.type === "panel" ? message.eventId === action.payload : false);
if (index === -1) {
return prevState;
}
return {
...prevState,
messages: prevState.messages.slice(0, index)
};
}
default:
return prevState;
}
};
export const useConversation = ({ conversationUuid, onMessage, onStartTyping, onStopTyping, onEpisodeComplete, onProblem, onStart, onReply, onResume, onTap, onAction, initialState, onStateChange, shouldResumeOnConnect, shouldStartOnConnect, speechConfig, sendIntermediateEvents })=>{
const [state, dispatch] = useReducer(reducer, initialState || {
inputValue: "",
isTyping: false,
messages: [],
mode: "chat"
});
const { inputValue, isTyping, messages, mode } = state;
useEffect(()=>{
if (onStateChange) {
onStateChange(state);
}
}, [
onStateChange,
state
]);
const handleMessage = useCallback(async (event)=>{
dispatch({
type: "MESSAGE_CHARACTER",
payload: event
});
if (onMessage) {
await onMessage(event);
}
}, [
onMessage
]);
const handleStartTyping = useCallback((event)=>{
dispatch({
type: "START_TYPING"
});
if (onStartTyping) {
onStartTyping(event);
}
}, [
onStartTyping
]);
const handleStopTyping = useCallback((event)=>{
dispatch({
type: "STOP_TYPING"
});
if (onStopTyping) {
onStopTyping(event);
}
}, [
onStopTyping
]);
const { start, reply, replyIntermediate, resume, tap, action } = useQueuedConversation({
conversationUuid,
onMessage: handleMessage,
onStartTyping: handleStartTyping,
onStopTyping: handleStopTyping,
onEpisodeComplete,
onProblem,
speechConfig
});
const playthroughContext = usePlaythroughContext();
const hasHandledMount = useRef(false);
useEffect(()=>{
if (playthroughContext?.connectionStatus === "connected" && !hasHandledMount.current) {
hasHandledMount.current = true;
if (shouldResumeOnConnect) {
resume();
}
if (shouldStartOnConnect) {
const event = shouldStartOnConnect === true ? {} : shouldStartOnConnect;
start(event);
}
}
/* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [
playthroughContext?.connectionStatus
]);
const handleStart = useCallback((event = {})=>{
dispatch({
type: "RESET"
});
if (onStart) {
onStart(event);
}
start(event);
}, [
onStart,
start
]);
const handleReply = useCallback((event)=>{
if (onReply) {
onReply(event);
}
dispatch({
type: "MESSAGE_PLAYER",
payload: {
type: "player",
timestamp: Date.now(),
message: {
text: event.text
}
}
});
reply(event);
}, [
onReply,
reply
]);
const handleTap = useCallback(()=>{
if (onTap) {
onTap();
}
tap();
}, [
onTap,
tap
]);
const handleAction = useCallback((event)=>{
if (onAction) {
onAction(event);
}
action(event);
}, [
onAction,
action
]);
const handleResume = useCallback(()=>{
if (onResume) {
onResume();
}
resume();
}, [
onResume,
resume
]);
const handleType = useCallback((text)=>{
if (sendIntermediateEvents) {
replyIntermediate({
text,
inputType: "keyboard"
});
}
dispatch({
type: "TYPE",
payload: text
});
}, [
replyIntermediate,
sendIntermediateEvents
]);
const [isRestarting, setIsRestarting] = useState(false);
const handleRestart = useCallback(async (eventId)=>{
if (playthroughContext && playthroughContext.playthrough) {
setIsRestarting(true);
try {
await playthroughContext.playthrough.restartFromEventId(eventId);
dispatch({
type: "RESTART",
payload: eventId
});
} finally{
setIsRestarting(false);
}
}
}, [
playthroughContext
]);
return {
inputValue,
isRestarting,
isTyping,
messages,
mode,
type: handleType,
start: handleStart,
reply: handleReply,
tap: handleTap,
action: handleAction,
resume: handleResume,
restart: handleRestart
};
};
const ConversationContext = /*#__PURE__*/ createContext(undefined);
export const Conversation = ({ children, ...props })=>{
const conversation = useConversation(props);
return /*#__PURE__*/ React.createElement(ConversationContext.Provider, {
value: conversation
}, children);
};
export const useConversationContext = ()=>useContext(ConversationContext);