FredrikOseberg / react-chatbot-kit

MIT License
297 stars 139 forks source link

Access messageText in custom message? #87

Open chtseac opened 2 years ago

chtseac commented 2 years ago

Currently the custom message system doesn't seem to have a way for the custom message to get the message text. I would want the text to be passed as prop or as a child. Maybe even make that any type so a payload can be passed.

My workaround is setting the info to the state (see https://github.com/FredrikOseberg/react-chatbot-kit/issues/6#issuecomment-689689264), and it only works when there is only one message created at a time.

const config = {
  initialMessages: [
    createCustomMessage('foo', 'SystemMessage'),
  ],
  state: {currentSystemMessage: 'You started the conversation.'},
  customMessages: {
    SystemMessage({state: {currentSystemMessage}}) {
      const [message, setMessage] = useState('');
      useEffect(() => setMessage(currentSystemMessage), []);
      return <div>--- System: {message} ---</div>
    },
  },
  botName: 'Agent',
};
class ActionProvider {
  constructor(
    createChatBotMessage,
    setStateFunc,
    createClientMessage,
    state,
    createCustomMessage
  ) {
    this.createChatBotMessage = createChatBotMessage;
    this.createClientMessage = createClientMessage;
    this.createCustomMessage = createCustomMessage;
    this.setState = setStateFunc;
    this.state = state;
  }
  createSystemMessage(text) {
    // https://github.com/FredrikOseberg/react-chatbot-kit/issues/87
    this.setState(({messages, ...rest}) => ({
      ...rest,
      messages: [...messages, this.createCustomMessage(text, 'SystemMessage')],
      currentSystemMessage: text,
    }));
  }
}
FredrikOseberg commented 2 years ago

@chtseac

You do actually receive a number of props in the custom message that you can use if I understand your use case correctly.

Here's the code that renders the custom message:

const renderCustomMessage = (messageObject: IMessage) => {
    const customMessage = customMessages[messageObject.type];

    const props = {
      setState,
      state,
      scrollIntoView,
      actionProvider,
    };

    if (messageObject.widget) {
      const widget = widgetRegistry.getWidget(messageObject.widget, {
        ...state,
        scrollIntoView,
      });
      return (
        <>
          {customMessage(props)}
          {widget ? widget : null}
        </>
      );
    }

    return customMessage(props);
  };

As you can see we pass the full state, setState function, actionProvider and a scrollIntoView function as props. You could simply access the full messages array on the state object and filter by messagetype in order to end up with an array of all the latest user messages. Or you could create a new array in state and push all of the user messages onto that array through the message and access them in your custom message.

chtseac commented 2 years ago

Thank you for the response!

Yeah, I've looked at that part of the source. My current problem is if I have two custom messages, how would I go about implementing that?

initialMessages: [
    createCustomMessage("You joined the game.", "system"),
    createCustomMessage("It is your turn.", "system")
]

In the current version, there is no way the custom message render functions can distinct themselves. They receive identical props. (The same goes for widgets, actually, say if we have many multiple choice questions, there's no way for the widgets to get which questions they belong to.)

A way to pass the custom widgets/messages a payload would be appreciated. At the very least, pass the current message object or the message id so that I can put a lookup object in the state.

On Wed, Oct 20, 2021, 23:59 Fredrik Strand Oseberg @.***> wrote:

@chtseac https://github.com/chtseac

You do actually receive a number of props in the custom message that you can use if I understand your use case correctly.

Here's the code that renders the custom message:

const renderCustomMessage = (messageObject: IMessage) => { const customMessage = customMessages[messageObject.type];

const props = {
  setState,
  state,
  scrollIntoView,
  actionProvider,
};

if (messageObject.widget) {
  const widget = widgetRegistry.getWidget(messageObject.widget, {
    ...state,
    scrollIntoView,
  });
  return (
    <>
      {customMessage(props)}
      {widget ? widget : null}
    </>
  );
}

return customMessage(props);

};

As you can see we pass the full state, setState function, actionProvider and a scrollIntoView function as props. You could simply access the full messages array on the state object and filter by messagetype in order to end up with an array of all the latest user messages. Or you could create a new array in state and push all of the user messages onto that array through the message and access them in your custom message.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/FredrikOseberg/react-chatbot-kit/issues/87#issuecomment-948314164, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKM626EA3RAXSNJYU5TEIDUH625ZANCNFSM5GML7AMA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

FredrikOseberg commented 2 years ago

@chtseac You're right. You'd need to create a unique identifier in state for this to work, and then it would update all of the other messages in the chat history. It would be good if you could pass a payload with the message. I'll look into ways of adding that, got some thoughts.

viktortsvil commented 11 months ago

Are there any updates on this? :)

UPD: I figured that I can pass payload when calling createCustomMessage function from the library as follows: createCustomMessage(reply.content, 'system', {payload: reply.content} ). This way the text to be displayed and the payload match.

I also defined customMessages field in the config file as follows:

this.customMessages = {
            system: (props) => {
                return <SystemMessage {...props} message={props.state.messages.find(msg => (msg.payload === props.payload))}/>
            },
        } 

where my SystemMessage component is defined in a simple way:

const SystemMessage = ({state, message}) => {
    return (<p>SYSTEM MESSAGE: {message.message}</p>);
}; 

This seems to work and not overwrite older custom message with the text of the new messages.

I am not sure if that's the best way to go about it, but I hope it helps whoever has the same issue :)

tpritsky commented 3 months ago

Are there any updates on this? :)

UPD: I figured that I can pass payload when calling createCustomMessage function from the library as follows: createCustomMessage(reply.content, 'system', {payload: reply.content} ). This way the text to be displayed and the payload match.

I also defined customMessages field in the config file as follows:

this.customMessages = {
            system: (props) => {
                return <SystemMessage {...props} message={props.state.messages.find(msg => (msg.payload === props.payload))}/>
            },
        } 

where my SystemMessage component is defined in a simple way:

const SystemMessage = ({state, message}) => {
    return (<p>SYSTEM MESSAGE: {message.message}</p>);
}; 

This seems to work and not overwrite older custom message with the text of the new messages.

I am not sure if that's the best way to go about it, but I hope it helps whoever has the same issue :)

Super helpful workaround, thanks! This should be added as a built-in feature