Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- [`[email protected]`](https://npmjs.com/package/webpack-cli/)
- [`[email protected]`](https://npmjs.com/package/webpack/)
- Fixed [#5446](https://github.com/microsoft/BotFramework-WebChat/issues/5446). Embedded `uuid` so `microsoft-cognitiveservices-speech-sdk` do not need to use dynamic loading, as this could fail in Webpack 4 environment, in PR [#5445](https://github.com/microsoft/BotFramework-WebChat/pull/5445), by [@compulim](https://github.com/compulim)
- - Improved latency of `ActivityTreeComposer` in PR [#5468](https://github.com/microsoft/BotFramework-WebChat/pull/5468) by [@pranavjoshi001](https://github.com/pranavjoshi001)

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import useNotifications from './useNotifications';
import usePerformCardAction from './usePerformCardAction';
import usePonyfill from './usePonyfill';
import usePostActivity from './usePostActivity';
import useReduceActivities from './useReduceActivities';
import useReferenceGrammarID from './useReferenceGrammarID';
import useRelativeTimeFormatter from './useRelativeTimeFormatter';
import useRenderAttachment from './useRenderAttachment';
Expand Down Expand Up @@ -113,6 +114,7 @@ export {
usePerformCardAction,
usePonyfill,
usePostActivity,
useReduceActivities,
useReferenceGrammarID,
useRelativeTimeFormatter,
useRenderAttachment,
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/hooks/useReduceActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import useReduceActivities from '../providers/ActivityTyping/private/useReduceActivities';

export default useReduceActivities;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { hooks, type ActivityComponentFactory } from 'botframework-webchat-api';
import type { WebChatActivity } from 'botframework-webchat-core';
import React, { useMemo, type ReactNode } from 'react';
import React, { useMemo, useRef, type ReactNode } from 'react';

import useMemoWithPrevious from '../../hooks/internal/useMemoWithPrevious';
import ActivityTreeContext from './private/Context';
Expand All @@ -13,7 +13,14 @@ import type { ActivityTreeContextType } from './private/Context';

type ActivityTreeComposerProps = Readonly<{ children?: ReactNode | undefined }>;

const { useActivities, useActivityKeys, useCreateActivityRenderer, useGetActivitiesByKey, useGetKeyByActivity } = hooks;
const {
useActivities,
useActivityKeys,
useCreateActivityRenderer,
useGetKeyByActivity,
useReduceActivities,
useGetActivityByKey
} = hooks;

const ActivityTreeComposer = ({ children }: ActivityTreeComposerProps) => {
const existingContext = useActivityTreeContext(false);
Expand All @@ -23,30 +30,48 @@ const ActivityTreeComposer = ({ children }: ActivityTreeComposerProps) => {
}

const [rawActivities] = useActivities();
const getActivitiesByKey = useGetActivitiesByKey();
const getKeyByActivity = useGetKeyByActivity();
const activityKeys = useActivityKeys();
const getActivityByKey = useGetActivityByKey();
const activityKeysSet = new Set(activityKeys[0]);

const activities = useMemo<readonly WebChatActivity[]>(() => {
const activities: WebChatActivity[] = [];
// Persistent Map to store activities by their keys
const activityMapRef = useRef<Readonly<Map<string, WebChatActivity>>>(
Object.freeze(new Map<string, WebChatActivity>())
);

const activities =
useReduceActivities<readonly WebChatActivity[]>((prevActivities = [], activity) => {
if (!activityKeys) {
return rawActivities;
}

if (!activityKeys) {
return rawActivities;
}
// This is better than looping through all activities as bunch of stream activities will be clubbed
// under single key and number of iteration will be significantly less.
for (const key of activityMapRef.current.keys()) {
if (!activityKeysSet.has(key)) {
activityMapRef.current.delete(key);
}
}

for (const activity of rawActivities) {
// If an activity has multiple revisions, display the latest revision only at the position of the first revision.
const activityKey = getKeyByActivity(activity);

// "Activities with same key" means "multiple revisions of same activity."
const activitiesWithSameKey = getActivitiesByKey(getKeyByActivity(activity));
// we always want last revision of activity whether it is single or multiple."
const lastActivity = getActivityByKey(activityKey);

// TODO: We may want to send all revisions of activity to the middleware so they can render UI to see previous revisions.
activitiesWithSameKey?.[0] === activity &&
activities.push(activitiesWithSameKey[activitiesWithSameKey.length - 1]);
}
if (lastActivity === activity) {
const activityMap = activityMapRef.current;

// Update or add the activity in the persistent Map
activityMap.set(activityKey, activity);

// Return the updated activities as an array
return Array.from(activityMap.values());
}

return Object.freeze(activities);
}, [activityKeys, getActivitiesByKey, getKeyByActivity, rawActivities]);
return prevActivities;
}) || [];

const createActivityRenderer: ActivityComponentFactory = useCreateActivityRenderer();

Expand Down
Loading