@telia-ace/widget-conversation 1.0.19 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -22
- package/dist/components/conversation/ui/loader.d.ts +1 -1
- package/dist/components/conversation/ui/message-list-items/video-request.d.ts +8 -0
- package/dist/conversation.js +150 -58
- package/dist/conversation.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +9 -2
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -58,32 +58,34 @@ const agent = conversation.createAgent({
|
|
|
58
58
|
|
|
59
59
|
## Writing to the conversation
|
|
60
60
|
|
|
61
|
+
***`print()` accepts an array, which is considered best practice when writing to the conversation. The previous way of posting an object is still currently be supported, but is considered to be deprecated.**
|
|
62
|
+
|
|
61
63
|
### Posting, updating and removing content
|
|
62
64
|
|
|
63
|
-
When managing content in the conversation you'll first need to create an `Agent`-object and then use its `print()` function. There are serveral types of entries in order to
|
|
65
|
+
When managing content in the conversation you'll first need to create an `Agent`-object and then use its `print()` function. There are serveral types of entries in order to accommodate different types of content.
|
|
64
66
|
|
|
65
|
-
The `print(
|
|
67
|
+
The `print(items: ConversationMessageItem[])` function let's you return a `ConversationEntry` message that can be used to update and/or remove the content from the conversation.
|
|
66
68
|
|
|
67
69
|
##### Example of simple text message
|
|
68
70
|
|
|
69
71
|
```ts
|
|
70
72
|
// print user message
|
|
71
|
-
conversation.user.print('Lorem ipsum');
|
|
73
|
+
conversation.user.print([['text', 'Lorem ipsum']]);
|
|
72
74
|
|
|
73
75
|
// print agent message
|
|
74
76
|
const agent = conversation.createAgent();
|
|
75
|
-
agent.print('Lorem ipsum');
|
|
77
|
+
agent.print([['text', 'Lorem ipsum']]);
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
##### Example of updating or removing content
|
|
79
81
|
|
|
80
82
|
```ts
|
|
81
83
|
const agent = conversation.createAgent();
|
|
82
|
-
const entry = agent.print('Hello world!');
|
|
84
|
+
const entry = agent.print([['text', 'Hello world!']]);
|
|
83
85
|
|
|
84
|
-
entry.update(
|
|
86
|
+
entry.update([
|
|
85
87
|
// ...
|
|
86
|
-
|
|
88
|
+
]);
|
|
87
89
|
|
|
88
90
|
entry.remove();
|
|
89
91
|
```
|
|
@@ -92,24 +94,55 @@ entry.remove();
|
|
|
92
94
|
|
|
93
95
|
You can post an entry into the conversation with actions. You can specify a title, body and available actions. This is only supported for the `Agent` and will be displayed as a system message.
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
| `
|
|
97
|
+
This can be either in the form of a "ButtonList" or an "LinkList" as demostrated below.
|
|
98
|
+
|
|
99
|
+
| Name | Type | Description |
|
|
100
|
+
| --------- | -------- | -------------------------------------------- |
|
|
101
|
+
| `header` | `string` | Header for the entry. |
|
|
102
|
+
| `actions` | `array` | Array of of available actions for the entry. |
|
|
100
103
|
|
|
101
104
|
##### Example of a list of actions
|
|
102
105
|
|
|
103
106
|
```ts
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
// example of button list
|
|
108
|
+
agent.print([
|
|
109
|
+
[
|
|
110
|
+
'button-list',
|
|
111
|
+
{
|
|
112
|
+
header: 'Download invoices',
|
|
113
|
+
actions: [
|
|
114
|
+
{
|
|
115
|
+
actionKey: 'invoice_190201',
|
|
116
|
+
label: 'Invoice 190201',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
actionKey: 'invoice_190301',
|
|
120
|
+
label: 'Invoice 190301',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
// here we use a link list instead, but with the same actions
|
|
128
|
+
agent.print([
|
|
129
|
+
[
|
|
130
|
+
'link-list',
|
|
131
|
+
{
|
|
132
|
+
header: 'Download invoices',
|
|
133
|
+
actions: [
|
|
134
|
+
{
|
|
135
|
+
actionKey: 'invoice_190201',
|
|
136
|
+
label: 'Invoice 190201',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
actionKey: 'invoice_190301',
|
|
140
|
+
label: 'Invoice 190301',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
]);
|
|
113
146
|
```
|
|
114
147
|
|
|
115
148
|
### Entry with a form
|
|
@@ -152,6 +185,56 @@ agent.print({
|
|
|
152
185
|
});
|
|
153
186
|
```
|
|
154
187
|
|
|
188
|
+
### Entry with a video call
|
|
189
|
+
|
|
190
|
+
Use the `video-request` item type to create an entry representing an incoming video call. This entry can also have a form attached, which uses the `FormBuilder`-method and is handled by the `conversation.form` event. It is only supported for an `Agent` and will be displayed as a system message.
|
|
191
|
+
|
|
192
|
+
| Name | Type | Description |
|
|
193
|
+
| -------------- | -------- | ----------------------------------------------- |
|
|
194
|
+
| `header` | `string` | Header for the entry. |
|
|
195
|
+
| `acceptLabel` | `string` | Label to be shown for the "accept call button" |
|
|
196
|
+
| `declineLabel` | `string` | Label to be shown for the "decline call button" |
|
|
197
|
+
|
|
198
|
+
##### Example of an entry for an incoming video call
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
agent.print([
|
|
202
|
+
[
|
|
203
|
+
'video-request',
|
|
204
|
+
{
|
|
205
|
+
header: 'Video call',
|
|
206
|
+
accept: 'Accept',
|
|
207
|
+
decline: 'Decline',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
]);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
When the user presses one of these buttons to accept or decline the call, an action will be dispatched with actionKey `accept-video-call` or `decline-video-call`, depending on which button was clicked.
|
|
214
|
+
|
|
215
|
+
Listening to these actions can be done as following:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
component.actions.watch('conversation.action', (input, next) => {
|
|
219
|
+
if (input.actionKey === 'accept-video-call') {
|
|
220
|
+
// this will render an overlay over the widget,
|
|
221
|
+
// and prepare the widget for displaying the actual call
|
|
222
|
+
component.actions.dispatch('show-overlay', {});
|
|
223
|
+
} else if (input.actionKey === 'decline-video-call') {
|
|
224
|
+
// ...
|
|
225
|
+
}
|
|
226
|
+
return next(input);
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Use an overlay over the conversation to render your video call. The `show-overlay`-action, as described above, accomplishes this. After the overlay is rendered and ready it will dispatch another event: `overlay-mounted`. Use the HTML element this generates to render your video call.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
component.actions.watch('overlay-mounted', (overlayElem, next) => {
|
|
234
|
+
// the overlayElem here may be used to render the ongoing call
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
155
238
|
### The loading and typing indicators
|
|
156
239
|
|
|
157
240
|
When you need to fetch data from external resource you should use the `loading()` function on the `ConversationProvider` to inform the user that something is about to happen. Even in cases when the response is available immediately it gives a better user experience to present a loading indicator for a short while.
|
|
@@ -181,7 +264,7 @@ System message in the conversation are sent by using the `print` method on the `
|
|
|
181
264
|
#### Example
|
|
182
265
|
|
|
183
266
|
```ts
|
|
184
|
-
conversation.print('Hello world!');
|
|
267
|
+
conversation.print([['text', 'Hello world!']]);
|
|
185
268
|
```
|
|
186
269
|
|
|
187
270
|
## Reading from the conversation
|
|
@@ -4,5 +4,5 @@ declare type Props = {
|
|
|
4
4
|
className?: string;
|
|
5
5
|
agentData?: ConversationMessageSender;
|
|
6
6
|
};
|
|
7
|
-
declare const ConversationLoader: React.
|
|
7
|
+
declare const ConversationLoader: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
|
|
8
8
|
export default ConversationLoader;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DispatchAction } from '@telia-ace/widget-ui';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import { VideoRequestProperties } from '../../../../types';
|
|
4
|
+
declare type Props = {
|
|
5
|
+
dispatch: DispatchAction;
|
|
6
|
+
} & VideoRequestProperties;
|
|
7
|
+
export declare const VideoRequest: FC<Props>;
|
|
8
|
+
export {};
|
package/dist/conversation.js
CHANGED
|
@@ -29,9 +29,9 @@ var __objRest = (source, exclude) => {
|
|
|
29
29
|
}
|
|
30
30
|
return target;
|
|
31
31
|
};
|
|
32
|
-
import { Input, Button, borderTabStyle, SymbolBadge, useDispatch, useProperties, useScroll, Typography, invertedHoverButton, List, buttonCss, Html as Html$1, genericHtml,
|
|
32
|
+
import { Input, Button, borderTabStyle, SymbolBadge, useDispatch, useProperties, useScroll, Typography, invertedHoverButton, List, buttonCss, Html as Html$1, genericHtml, ItemList, itemList, useComponent, list, headingElement, WidgetOverlayOutlet } from "@telia-ace/widget-ui";
|
|
33
33
|
import { appendClassNames, uuid } from "@telia-ace/widget-utilities";
|
|
34
|
-
import React, { useState, useEffect, Suspense, useCallback, useRef } from "react";
|
|
34
|
+
import React, { useState, useEffect, forwardRef, Suspense, useMemo, useCallback, useRef } from "react";
|
|
35
35
|
import styled, { css, keyframes } from "styled-components";
|
|
36
36
|
import { FormComponent } from "@telia-ace/widget-forms";
|
|
37
37
|
import { ConversationMessageListItemType, ConversationMessageType } from "./index.js";
|
|
@@ -146,7 +146,7 @@ const ConversationInput = ({ className }) => {
|
|
|
146
146
|
"aria-label": inputPlaceholder,
|
|
147
147
|
placeholder: inputPlaceholder,
|
|
148
148
|
onFocusChange: setInputFocus
|
|
149
|
-
}), /* @__PURE__ */ React.createElement(StyledButton, {
|
|
149
|
+
}), /* @__PURE__ */ React.createElement(StyledButton$1, {
|
|
150
150
|
disabled: inputDisabled,
|
|
151
151
|
onClick: submit,
|
|
152
152
|
title: sendButtonLabel,
|
|
@@ -205,7 +205,7 @@ const StyledInput = styled(Input)`
|
|
|
205
205
|
font-style: italic;
|
|
206
206
|
}
|
|
207
207
|
`;
|
|
208
|
-
const StyledButton = styled(Button)`
|
|
208
|
+
const StyledButton$1 = styled(Button)`
|
|
209
209
|
flex: 0 0 50px;
|
|
210
210
|
align-self: stretch;
|
|
211
211
|
background-color: transparent;
|
|
@@ -221,7 +221,7 @@ const StyledButton = styled(Button)`
|
|
|
221
221
|
}};
|
|
222
222
|
}
|
|
223
223
|
`;
|
|
224
|
-
const SecondaryActionButton = styled(StyledButton)`
|
|
224
|
+
const SecondaryActionButton = styled(StyledButton$1)`
|
|
225
225
|
border-right: ${(p) => p.theme.border};
|
|
226
226
|
margin: ${(p) => {
|
|
227
227
|
var _a;
|
|
@@ -334,6 +334,10 @@ const LoaderAnimation = keyframes`
|
|
|
334
334
|
const Loader = styled.div`
|
|
335
335
|
display: flex;
|
|
336
336
|
justify-content: center;
|
|
337
|
+
padding: 0 0 ${(p) => {
|
|
338
|
+
var _a;
|
|
339
|
+
return (_a = p.theme.sizes) == null ? void 0 : _a.normal;
|
|
340
|
+
}};
|
|
337
341
|
> div {
|
|
338
342
|
height: 10px;
|
|
339
343
|
width: 10px;
|
|
@@ -355,6 +359,7 @@ const Loader = styled.div`
|
|
|
355
359
|
const ContentBundle = styled.div`
|
|
356
360
|
grid-area: content;
|
|
357
361
|
max-width: 100%;
|
|
362
|
+
overflow: hidden;
|
|
358
363
|
`;
|
|
359
364
|
const MessageTitle = styled(Typography)`
|
|
360
365
|
font-size: ${(p) => {
|
|
@@ -544,21 +549,14 @@ const StyledActionButton = styled.button`
|
|
|
544
549
|
}}
|
|
545
550
|
}
|
|
546
551
|
`;
|
|
547
|
-
const ConversationLoader = ({ className, agentData }) => {
|
|
548
|
-
return /* @__PURE__ */ React.createElement(ContentBundle,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
attrs
|
|
552
|
+
const ConversationLoader = forwardRef(({ className, agentData }, ref) => {
|
|
553
|
+
return /* @__PURE__ */ React.createElement(ContentBundle, {
|
|
554
|
+
ref
|
|
555
|
+
}, /* @__PURE__ */ React.createElement(MessageContainer, null, /* @__PURE__ */ React.createElement(MessageContent, null, /* @__PURE__ */ React.createElement(Loader, null, /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement("div", null)))));
|
|
552
556
|
});
|
|
553
|
-
const htmlParseInstructions = {
|
|
554
|
-
transform: {
|
|
555
|
-
img: imageLightboxRenderer
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
557
|
const Html = ({ body }) => {
|
|
559
558
|
return /* @__PURE__ */ React.createElement(StyledHtml, {
|
|
560
|
-
html: body
|
|
561
|
-
htmlParseInstruction: htmlParseInstructions
|
|
559
|
+
html: body
|
|
562
560
|
});
|
|
563
561
|
};
|
|
564
562
|
const StyledHtml = styled(Html$1)`
|
|
@@ -835,6 +833,9 @@ const StyledLinkButton = styled(Button)`
|
|
|
835
833
|
var _a;
|
|
836
834
|
return (_a = p.theme.colors) == null ? void 0 : _a.primary;
|
|
837
835
|
}};
|
|
836
|
+
max-width: 100%;
|
|
837
|
+
text-overflow: ellipsis;
|
|
838
|
+
overflow: hidden;
|
|
838
839
|
|
|
839
840
|
${(p) => {
|
|
840
841
|
var _a, _b;
|
|
@@ -877,6 +878,114 @@ const Text = ({ text }) => {
|
|
|
877
878
|
const StyledTypography = styled(Typography)`
|
|
878
879
|
${messageContentPadding}
|
|
879
880
|
`;
|
|
881
|
+
const VideoRequest = ({
|
|
882
|
+
header = "Video call",
|
|
883
|
+
acceptLabel = "Accept",
|
|
884
|
+
declineLabel = "Decline",
|
|
885
|
+
dispatch
|
|
886
|
+
}) => {
|
|
887
|
+
const actions = useMemo(() => {
|
|
888
|
+
return [
|
|
889
|
+
{
|
|
890
|
+
label: acceptLabel,
|
|
891
|
+
actionKey: "accept-video-call",
|
|
892
|
+
symbol: {
|
|
893
|
+
type: "Svg",
|
|
894
|
+
content: "video"
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
label: declineLabel,
|
|
899
|
+
actionKey: "decline-video-call",
|
|
900
|
+
symbol: {
|
|
901
|
+
type: "Svg",
|
|
902
|
+
content: "close"
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
];
|
|
906
|
+
}, [acceptLabel, declineLabel]);
|
|
907
|
+
return /* @__PURE__ */ React.createElement(StyledItemList, {
|
|
908
|
+
header,
|
|
909
|
+
items: actions,
|
|
910
|
+
renderItem: (item) => {
|
|
911
|
+
const bgColor = item.actionKey === "accept-video-call" ? "#70c48e" : "#da5c85";
|
|
912
|
+
return /* @__PURE__ */ React.createElement("li", {
|
|
913
|
+
key: item.actionKey
|
|
914
|
+
}, /* @__PURE__ */ React.createElement(StyledButton, {
|
|
915
|
+
onClick: () => {
|
|
916
|
+
dispatch("action", item);
|
|
917
|
+
},
|
|
918
|
+
"aria-label": item.label,
|
|
919
|
+
title: item.label,
|
|
920
|
+
bgColor
|
|
921
|
+
}, /* @__PURE__ */ React.createElement(SymbolBadge, {
|
|
922
|
+
symbol: item.symbol,
|
|
923
|
+
size: 25
|
|
924
|
+
})));
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
};
|
|
928
|
+
const StyledItemList = styled(ItemList)`
|
|
929
|
+
${itemList}
|
|
930
|
+
> ul {
|
|
931
|
+
display: flex;
|
|
932
|
+
flex-wrap: wrap;
|
|
933
|
+
gap: ${(p) => {
|
|
934
|
+
var _a;
|
|
935
|
+
return (_a = p.theme.sizes) == null ? void 0 : _a.normal;
|
|
936
|
+
}};
|
|
937
|
+
> li {
|
|
938
|
+
margin: 0;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
${messageContentPadding}
|
|
942
|
+
`;
|
|
943
|
+
const StyledButton = styled(Button)`
|
|
944
|
+
${buttonCss};
|
|
945
|
+
|
|
946
|
+
background-color: ${(p) => p.bgColor};
|
|
947
|
+
display: flex;
|
|
948
|
+
justify-content: center;
|
|
949
|
+
align-items: center;
|
|
950
|
+
color: #ffffff;
|
|
951
|
+
width: 46px;
|
|
952
|
+
height: 46px;
|
|
953
|
+
border-radius: 100%;
|
|
954
|
+
|
|
955
|
+
&:focus {
|
|
956
|
+
${(p) => {
|
|
957
|
+
var _a;
|
|
958
|
+
return ((_a = p.theme.accessibility) == null ? void 0 : _a.isTabbing) && css`
|
|
959
|
+
${borderTabStyle}
|
|
960
|
+
background-color: transparent;
|
|
961
|
+
|
|
962
|
+
i,
|
|
963
|
+
svg {
|
|
964
|
+
color: ${(p2) => {
|
|
965
|
+
var _a2;
|
|
966
|
+
return (_a2 = p2.theme.colors) == null ? void 0 : _a2.primary;
|
|
967
|
+
}};
|
|
968
|
+
|
|
969
|
+
path {
|
|
970
|
+
stroke: ${(p2) => {
|
|
971
|
+
var _a2;
|
|
972
|
+
return (_a2 = p2.theme.colors) == null ? void 0 : _a2.primary;
|
|
973
|
+
}};
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
`;
|
|
977
|
+
}}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
i,
|
|
981
|
+
svg {
|
|
982
|
+
color: #fff;
|
|
983
|
+
|
|
984
|
+
path {
|
|
985
|
+
stroke: #fff;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
`;
|
|
880
989
|
const mapItem = ([type, content]) => {
|
|
881
990
|
return { type, content, id: uuid() };
|
|
882
991
|
};
|
|
@@ -907,6 +1016,11 @@ const MessageList = ({ items, dispatch }) => {
|
|
|
907
1016
|
dispatch
|
|
908
1017
|
}));
|
|
909
1018
|
}
|
|
1019
|
+
case ConversationMessageListItemType.VideoRequest: {
|
|
1020
|
+
return /* @__PURE__ */ React.createElement(VideoRequest, __spreadProps(__spreadValues({}, item.content), {
|
|
1021
|
+
dispatch
|
|
1022
|
+
}));
|
|
1023
|
+
}
|
|
910
1024
|
default: {
|
|
911
1025
|
return /* @__PURE__ */ React.createElement(Custom, {
|
|
912
1026
|
type: item.type,
|
|
@@ -1036,56 +1150,31 @@ const Message = ({ content, isUser, stacked }) => {
|
|
|
1036
1150
|
}));
|
|
1037
1151
|
};
|
|
1038
1152
|
const ConversationContent = () => {
|
|
1039
|
-
const lastEntry = useRef(
|
|
1153
|
+
const lastEntry = useRef(null);
|
|
1040
1154
|
const [scrollableCss, viewElem] = useScroll(true);
|
|
1041
1155
|
const { messages, loading, typingActors } = useProperties();
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
lastEntry2.current.focus();
|
|
1049
|
-
}
|
|
1050
|
-
if (lastEntry2.current.id.indexOf("typing_") !== -1 && viewElem.current.scrollTopMax - viewElem.current.scrollTop > lastEntry2.current.clientHeight + 100) {
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
setTimeout(() => {
|
|
1054
|
-
if (lastEntry2.current) {
|
|
1055
|
-
lastEntry2.current.scrollIntoView({
|
|
1056
|
-
block: "end",
|
|
1057
|
-
behavior: smooth ? "smooth" : "auto"
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
}, 150);
|
|
1156
|
+
const scrollView = useCallback(() => {
|
|
1157
|
+
if (lastEntry.current) {
|
|
1158
|
+
lastEntry.current.scrollIntoView({
|
|
1159
|
+
block: "end",
|
|
1160
|
+
behavior: "smooth"
|
|
1161
|
+
});
|
|
1061
1162
|
}
|
|
1062
1163
|
}, [lastEntry]);
|
|
1063
1164
|
useEffect(() => {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const typingActorCountChanged = typingActors.length !== (previousTypingActors == null ? void 0 : previousTypingActors.length);
|
|
1067
|
-
const messageContentCountChanged = messages.reduce((changed, message, index) => {
|
|
1068
|
-
var _a;
|
|
1069
|
-
if (((_a = previousMessages == null ? void 0 : previousMessages[index]) == null ? void 0 : _a.content.length) !== message.content.length) {
|
|
1070
|
-
return true;
|
|
1071
|
-
}
|
|
1072
|
-
return changed;
|
|
1073
|
-
}, false);
|
|
1074
|
-
if (messageCountChanged || messageContentCountChanged || typingActorCountChanged) {
|
|
1075
|
-
scrollView(lastEntry);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1165
|
+
const handler = setTimeout(scrollView, 250);
|
|
1166
|
+
return () => clearTimeout(handler);
|
|
1078
1167
|
}, [messages, typingActors]);
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
});
|
|
1168
|
+
const isLoadingOrTyping = useMemo(() => {
|
|
1169
|
+
return !!typingActors.length || loading;
|
|
1170
|
+
}, [typingActors, loading]);
|
|
1083
1171
|
const renderTypingActors = () => {
|
|
1084
1172
|
if (typingActors && typingActors.length) {
|
|
1085
1173
|
return typingActors.map((actor, index) => {
|
|
1174
|
+
const isLast = index === typingActors.length - 1;
|
|
1086
1175
|
return /* @__PURE__ */ React.createElement(MessageEntry, {
|
|
1087
1176
|
id: `typing_${actor.name}`,
|
|
1088
|
-
ref:
|
|
1177
|
+
ref: loading ? null : isLast ? lastEntry : null,
|
|
1089
1178
|
key: `typing_${actor.name}`
|
|
1090
1179
|
}, /* @__PURE__ */ React.createElement(AgentMessage, {
|
|
1091
1180
|
timestamp: Date.now(),
|
|
@@ -1101,13 +1190,16 @@ const ConversationContent = () => {
|
|
|
1101
1190
|
ref: viewElem
|
|
1102
1191
|
}, messages && messages.length ? messages.map((message, index) => {
|
|
1103
1192
|
const { type, timestamp, sender, content } = message;
|
|
1193
|
+
const isLast = index === messages.length - 1;
|
|
1104
1194
|
return /* @__PURE__ */ React.createElement(MessageEntry, {
|
|
1105
1195
|
className: "humany-conversation-entry",
|
|
1106
1196
|
tabIndex: -1,
|
|
1107
1197
|
key: `${type}_${sender == null ? void 0 : sender.name}_${timestamp}_${content[0].key}`,
|
|
1108
|
-
ref:
|
|
1198
|
+
ref: isLoadingOrTyping ? null : isLast ? lastEntry : null
|
|
1109
1199
|
}, /* @__PURE__ */ React.createElement(ConversationMessage, __spreadValues({}, message)));
|
|
1110
|
-
}) : null, renderTypingActors(), loading && /* @__PURE__ */ React.createElement(MessageEntry, null, /* @__PURE__ */ React.createElement(ConversationLoader,
|
|
1200
|
+
}) : null, renderTypingActors(), loading && /* @__PURE__ */ React.createElement(MessageEntry, null, /* @__PURE__ */ React.createElement(ConversationLoader, {
|
|
1201
|
+
ref: lastEntry
|
|
1202
|
+
}))), /* @__PURE__ */ React.createElement(ConversationInput, null));
|
|
1111
1203
|
};
|
|
1112
1204
|
const Scrollable = styled.div`
|
|
1113
1205
|
display: flex;
|