Skip to content

Commit 3d5c18f

Browse files
committed
perf: remove intermediate activities after stream ends
1 parent cd39b84 commit 3d5c18f

2 files changed

Lines changed: 35 additions & 18 deletions

File tree

packages/api-graph/src/private/GraphProvider.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,21 @@ function GraphProvider(props: GraphProviderProps) {
7676
};
7777
}, [graph, setOrderedActivityNodes]);
7878

79-
const orderedActivitiesState = useMemo<readonly [readonly WebChatActivity[]]>(
80-
() =>
81-
Object.freeze([
82-
Object.freeze(
83-
orderedActivityNodes.map(
84-
node => node['urn:microsoft:webchat:direct-line-activity:raw-json'][0]['@value'] as WebChatActivity
85-
)
86-
)
87-
] as const),
88-
[orderedActivityNodes]
89-
);
79+
const orderedActivitiesState = useMemo<readonly [readonly WebChatActivity[]]>(() => {
80+
// Filter out stale graph nodes for activities no longer in Redux (e.g. pruned livestream revisions).
81+
// The graph does not support deletion, so stale nodes linger after computeSortedActivities prunes them.
82+
const { activities: storeActivities } = store.getState();
83+
const validActivitySet = new Set<WebChatActivity>(storeActivities);
84+
const activities: WebChatActivity[] = [];
85+
86+
for (const node of orderedActivityNodes) {
87+
const activity = node['urn:microsoft:webchat:direct-line-activity:raw-json'][0]['@value'] as WebChatActivity;
88+
89+
validActivitySet.has(activity) && activities.push(activity);
90+
}
91+
92+
return Object.freeze([Object.freeze(activities)] as const);
93+
}, [orderedActivityNodes, store]);
9094

9195
const context = useMemo<GraphContextType>(
9296
() =>

packages/core/src/reducers/activities/sort/private/computeSortedActivities.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
1-
import type { Activity, State } from '../types';
1+
import type { Activity, LivestreamSessionMap, State } from '../types';
2+
3+
function* yieldSessionActivities(
4+
session: NonNullable<ReturnType<LivestreamSessionMap['get']>>,
5+
activityMap: State['activityMap']
6+
): Generator<Activity> {
7+
if (session.finalized) {
8+
// After finalization, only yield the final revision — intermediate revisions are pruned.
9+
// eslint-disable-next-line no-magic-numbers
10+
const lastEntry = session.activities.at(-1);
11+
12+
lastEntry && (yield activityMap.get(lastEntry.activityLocalId)!.activity);
13+
} else {
14+
for (const activityEntry of session.activities) {
15+
yield activityMap.get(activityEntry.activityLocalId)!.activity;
16+
}
17+
}
18+
}
219

320
export default function computeSortedActivities(
421
temporalState: Pick<State, 'activityMap' | 'howToGroupingMap' | 'livestreamSessionMap' | 'sortedChatHistoryList'>
@@ -20,17 +37,13 @@ export default function computeSortedActivities(
2037
} else {
2138
howToPartEntry.type satisfies 'livestream session';
2239

23-
for (const activityEntry of livestreamSessionMap.get(howToPartEntry.livestreamSessionId)!.activities) {
24-
yield activityMap.get(activityEntry.activityLocalId)!.activity;
25-
}
40+
yield* yieldSessionActivities(livestreamSessionMap.get(howToPartEntry.livestreamSessionId)!, activityMap);
2641
}
2742
}
2843
} else {
2944
sortedEntry.type satisfies 'livestream session';
3045

31-
for (const activityEntry of livestreamSessionMap.get(sortedEntry.livestreamSessionId)!.activities) {
32-
yield activityMap.get(activityEntry.activityLocalId)!.activity;
33-
}
46+
yield* yieldSessionActivities(livestreamSessionMap.get(sortedEntry.livestreamSessionId)!, activityMap);
3447
}
3548
}
3649
})()

0 commit comments

Comments
 (0)