Skip to content

Commit 02a980d

Browse files
committed
feat: markdown streaming
1 parent cd39b84 commit 02a980d

17 files changed

Lines changed: 947 additions & 106 deletions
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<title>HTML Streaming: Streaming Support</title>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<link href="./css/pauseAnimation.css" rel="stylesheet" type="text/css" />
7+
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
8+
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
9+
<script crossorigin="anonymous" src="/test-harness.js"></script>
10+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
12+
</head>
13+
<body>
14+
<main id="webchat"></main>
15+
<script type="module">
16+
const streamingFormat = new URL(window.location.href).searchParams.get('streamingFormat') ?? 'channelData'; // can be entity or channelData
17+
18+
function addLivestreamingMetadata(activity, livestreamingMetadata) {
19+
return streamingFormat === 'entity'
20+
? {
21+
...activity,
22+
entities: [...(activity.entities ?? []), { ...livestreamingMetadata, type: 'streaminfo' }]
23+
}
24+
: {
25+
...activity,
26+
channelData: { ...activity.channelData, ...livestreamingMetadata }
27+
};
28+
}
29+
30+
run(async function () {
31+
const {
32+
React: { createElement },
33+
ReactDOM: { render },
34+
WebChat: {
35+
Components: { BasicWebChat, Composer },
36+
decorator: { WebChatDecorator },
37+
hooks: { useActivityKeys, useGetActivitiesByKey },
38+
renderMarkdown
39+
}
40+
} = window; // Imports in UMD fashion.
41+
42+
if (new URLSearchParams(window.location.search).get('disableStreamingSupport') === 'true') {
43+
delete renderMarkdown.createStreamingRenderer;
44+
}
45+
46+
const { directLine, store } = testHelpers.createDirectLineEmulator();
47+
let currentActivityKeysWithId = [];
48+
49+
const Monitor = () => {
50+
const [activityKeys] = useActivityKeys();
51+
const getActivitiesByKey = useGetActivitiesByKey();
52+
53+
currentActivityKeysWithId = Object.freeze(
54+
activityKeys.map(key => [key, getActivitiesByKey(key).map(({ id }) => id)])
55+
);
56+
57+
return false;
58+
};
59+
60+
render(
61+
createElement(
62+
WebChatDecorator,
63+
{},
64+
createElement(
65+
Composer,
66+
{
67+
directLine,
68+
store
69+
},
70+
createElement(BasicWebChat),
71+
createElement(Monitor)
72+
)
73+
),
74+
document.getElementById('webchat')
75+
);
76+
77+
await pageConditions.uiConnected();
78+
79+
// SETUP: Bot sent a message.
80+
await directLine.emulateIncomingActivity({
81+
id: 'a-00001',
82+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
83+
text: 'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.',
84+
type: 'message'
85+
});
86+
87+
let firstActivityKey = currentActivityKeysWithId[0][0];
88+
89+
// WHEN: Bot is typing a message.
90+
const firstTypingActivityId = 't-00001';
91+
92+
await directLine.emulateIncomingActivity(
93+
addLivestreamingMetadata(
94+
{
95+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
96+
id: firstTypingActivityId,
97+
text: 'A quick\n\n',
98+
type: 'typing'
99+
},
100+
{ streamSequence: 1, streamType: 'streaming' }
101+
)
102+
);
103+
104+
let secondActivityKey = currentActivityKeysWithId[1][0];
105+
106+
// THEN: Should display 2 messages.
107+
await pageConditions.numActivitiesShown(2);
108+
expect(pageElements.typingIndicator()).toBeFalsy();
109+
expect(pageElements.activityContents()[0]).toHaveProperty(
110+
'textContent',
111+
'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.'
112+
);
113+
expect(pageElements.activityContents()[1]).toHaveProperty('textContent', 'A quick');
114+
await host.snapshot('local');
115+
116+
// THEN: Should have 2 activity keys.
117+
expect(currentActivityKeysWithId).toEqual([
118+
[firstActivityKey, ['a-00001']],
119+
[secondActivityKey, ['t-00001']]
120+
]);
121+
122+
// ---
123+
124+
// WHEN: Bot continue typing the message.
125+
await directLine.emulateIncomingActivity(
126+
addLivestreamingMetadata(
127+
{
128+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
129+
id: 't-00002',
130+
text: 'A quick\n\nbrown fox\n\n',
131+
type: 'typing'
132+
},
133+
{ streamId: firstTypingActivityId, streamSequence: 2, streamType: 'streaming' }
134+
)
135+
);
136+
137+
// THEN: Should display 2 messages.
138+
await pageConditions.numActivitiesShown(2);
139+
expect(pageElements.typingIndicator()).toBeFalsy();
140+
expect(pageElements.activityContents()[0]).toHaveProperty(
141+
'textContent',
142+
'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.'
143+
);
144+
expect(pageElements.activityContents()[1]).toHaveProperty('textContent', 'A quick\nbrown fox');
145+
await host.snapshot('local');
146+
147+
// THEN: Should have 2 activity keys only.
148+
expect(currentActivityKeysWithId).toEqual([
149+
[firstActivityKey, ['a-00001']],
150+
[secondActivityKey, ['t-00001', 't-00002']]
151+
]);
152+
153+
// ---
154+
155+
// WHEN: Bot continue typing the message.
156+
await directLine.emulateIncomingActivity(
157+
addLivestreamingMetadata(
158+
{
159+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
160+
id: 't-00003',
161+
text: 'A quick\n\nbrown fox\n\njumped over\n\n',
162+
type: 'typing'
163+
},
164+
{ streamId: firstTypingActivityId, streamSequence: 3, streamType: 'streaming' }
165+
)
166+
);
167+
168+
// THEN: Should display 2 messages.
169+
await pageConditions.numActivitiesShown(2);
170+
expect(pageElements.typingIndicator()).toBeFalsy();
171+
expect(pageElements.activityContents()[0]).toHaveProperty(
172+
'textContent',
173+
'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.'
174+
);
175+
expect(pageElements.activityContents()[1]).toHaveProperty('textContent', 'A quick\nbrown fox\njumped over');
176+
await host.snapshot('local');
177+
178+
// THEN: Should have 2 activity keys.
179+
expect(currentActivityKeysWithId).toEqual([
180+
[firstActivityKey, ['a-00001']],
181+
[secondActivityKey, ['t-00001', 't-00002', 't-00003']]
182+
]);
183+
184+
// WHEN: Bot finished typing the message.
185+
await directLine.emulateIncomingActivity(
186+
addLivestreamingMetadata(
187+
{
188+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
189+
id: 'a-00002',
190+
text: 'A quick\n\nbrown fox\n\njumped over\n\nthe lazy dogs.',
191+
type: 'message'
192+
},
193+
{ streamId: firstTypingActivityId, streamType: 'final' }
194+
)
195+
);
196+
197+
// THEN: Should display 2 messages.
198+
await pageConditions.numActivitiesShown(2);
199+
expect(pageElements.typingIndicator()).toBeFalsy();
200+
expect(pageElements.activityContents()[0]).toHaveProperty(
201+
'textContent',
202+
'Adipisicing cupidatat eu Lorem anim ut aute magna occaecat id cillum.'
203+
);
204+
expect(pageElements.activityContents()[1]).toHaveProperty(
205+
'textContent',
206+
'A quick\nbrown fox\njumped over\nthe lazy dogs.'
207+
);
208+
await host.snapshot('local');
209+
210+
// THEN: Should have 2 activity keys.
211+
expect(currentActivityKeysWithId).toEqual([
212+
[firstActivityKey, ['a-00001']],
213+
[secondActivityKey, ['t-00001', 't-00002', 't-00003', 'a-00002']]
214+
]);
215+
});
216+
</script>
217+
</body>
218+
</html>
11.5 KB
Loading
12.6 KB
Loading
13.8 KB
Loading
16.5 KB
Loading
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>HTML Streaming: No Streaming Support</title>
5+
<script>
6+
location = './html-chunk?disableStreamingSupport=true';
7+
</script>
8+
</head>
9+
<body></body>
10+
</html>
11.5 KB
Loading
12.6 KB
Loading
13.8 KB
Loading
16.5 KB
Loading

0 commit comments

Comments
 (0)