microsoft / BotFramework-WebChat

A highly-customizable web-based client for Azure Bot Services.
https://www.botframework.com/
MIT License
1.58k stars 1.53k forks source link

RFC: Architecture of chat adapter and supporting different chat providers #3713

Open compulim opened 3 years ago

compulim commented 3 years ago

Feature Request

To ready Web Chat into the future, I am proposing an architecture to allow Web Chat to operate on first party and third party chat providers.

Related issues:

Today

Today, Web Chat is quite coupled with the behavior of Direct Line protocol (implemented by Azure Bot Services). Especially, the following behaviors are built into Web Chat (non-exhaustive):

These behaviors are built into Redux saga of Web Chat. We need to move them out into a separate Direct Line chat adapter.

Tomorrow

I am proposing a stateful approach of chat adapter. For stateful, I mean the chat adapter is responsible for storing all activities in the transcript. This includes: received activities, sending activities, and sent activities.

In this approach, Web Chat will not in the business of (non-exhaustive):

Also, core features of Web Chat will not requires "channel data". If the chat adapter support backchannel data, developer can use Web Chat to send backchannel data. However, it is up to the chat adapter on how to send these data:

Restrictions

To provide best user experience, there are restrictions on the design of chat adapter:

Potential chat adapter interface

Internally, it would work like this:

ReactDOM.render(
  <ACSChatAdapter endpointURL="..." token="...">
    {(chatAdapterProps: ChatAdapterProps) => <ReactWebChat {...chatAdapterProps} styleOptions={...} />}
  </ACSChatAdapter>,
  document.getElementById('root')
);

type ChannelDataExtension = {
  channelData: {
    'webchat:avatar:initials'?: string;
    'webchat:avatar:image'?: string;
    'webchat:display-name'?: string | '__BOT__';
    'webchat:delivery-status': 'error' | 'sending' | 'sent';
    'webchat:key': string;
    'webchat:read-by'?: 'some' | 'all';
    'webchat:tracking-number'?: string;
    'webchat:who': 'channel' | 'others' | 'self';
  };
};

type Activity = DirectLineActivity & ChannelDataExtension;

type Notification = {
  alt?: string;
  data?: any;
  id: string;
  level?: string;
  message?: string;
};

type ConnectivityStatusNotification = Notification & {
  id: 'connectivitystatus';
  data: 'connected' | 'connecting' | 'fatal' | 'reconnecting';
};

type ChatAdapterProps = {
  activities: Activity[];
  notifications?: Notification[];
  userId?: string;
  username?: string;

  emitTypingIndicator?: () => void;
  resend?: (trackingNumber: string) => string;
  returnReadReceipt?: (activityKey: string) => void;
  sendEvent?: (name: string, value: any) => string;
  sendFiles?: (files: File[]) => string;
  sendMessage?: (message: string) => string;
  sendMessageBack?: (value: any, text: string, displayText: string) => string;
  sendPostBack?: (value: any) => string;

  typingUsers?: {
    [userId: string]: {
      at: number; // Change to Date
      name?: string;
      who: 'others' | 'self';
    };
  };

  // Direct Line specific props
  directLineReferenceGrammarId?: string;
  getDirectLineOAuthCodeChallenge: () => Promise<string>;
};

Although not required, a chat adapter should minimally implement both activities and sendMessage. For example,

ReactDOM.render(
  <ReactWebChat 
    activities={[
      channelData: {
        'webchat:key': 'id-1'
        'webchat:who': 'others'
      },
      text: 'Hello, World!'
      type: 'message'
    ]}
    sendMessage={message => alert(message)}
    styleOptions={...} 
  />,
  document.getElementById('root')
);

Bridging to DirectLineJS interface

Internally, if directLine object is passed, along with optional store parameter, we will use the older botframework-webchat-core to instantiate a Redux store, and use a <LegacyChatAdapterBridge> to bridge it from Redux store to new chat adapter interface.

Internally,

BridgeableWebChat({ directLine, store, ...props }) {
  return (
    directLine ?
      <LegacyChatAdapterBridge directLine={directLine} store={store}>
        {chatAdapterProps => <ReactWebChat {...chatAdapterProps} {...props} />}
      </LegacyChatAdapterBridge>
    :
      <ReactWebChat {...props} />
  );
}

const LegacyChatAdapterBridge = ({ directLine, store }) => {
  const { dispatch } = store;
  const activities = useSelector(({ activities }) => activities); // For clarity only, we actually use <Redux.Provider> before useSelector.
  const sendMessage = useCallback(message => dispatch({ payload: message, type: 'SEND_MESSAGE' }), [dispatch])

  // Also for other chat adapter interface:
  const notifications = [...];
  const sendEvent = useCallback(...);
  const sendFiles = useCallback(...);
  const sendMessageBack = useCallback(...);
  const sendPostBack = useCallback(...);

  return children({ activities, notifications, sendEvent, sendFiles, sendMessage, sendMessageBack, sendPostBack });
}

MVP for ACS chat adapter

Web Chat side change

Additional logics need to add on top of ACS chat adapter

Comments to ACS

General

Related to read receipts

Related to useChatMessages

(Some questions in this section is obsoleted as ACS is moving to another style of API.)

Related to useSendMessage

(Some questions in this section is obsoleted as ACS is moving to another style of API.)

Related to useTypingUsers and useSendTypingNotification

(Some questions in this section is obsoleted as ACS is moving to another style of API.)

Related to other hooks or providers

Outgoing messages appended in the middle

If a message is being sent while fetching user messages, it could be added in the middle of the transcript

image

Perceived order of messages could differs between clients

This is probably SDK issue. Right-hand side is the sender, although it is sending from 1 to 0, the transcript was perceived in a random order. Left-hand side is the receiver.

image

After token refresh or reloading the conversation history, both sender and receiver perceive the conversation with a correct order.

image

(To be continued)

[feature-request]

corinagum commented 3 years ago

This isn't a customer-reported issue per se, right? Can we remove customer reported and Bot Services?

Do we want a new Area label regarding adapters?