@nyaruka/temba-components 0.136.1 → 0.137.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/demo/components/webchat/example.html +2 -2
  3. package/dist/temba-components.js +487 -551
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/display/Chat.js +123 -44
  6. package/out-tsc/src/display/Chat.js.map +1 -1
  7. package/out-tsc/src/events/eventRenderers.js +442 -0
  8. package/out-tsc/src/events/eventRenderers.js.map +1 -0
  9. package/out-tsc/src/flow/Editor.js +3 -2
  10. package/out-tsc/src/flow/Editor.js.map +1 -1
  11. package/out-tsc/src/flow/NodeEditor.js +0 -1
  12. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  13. package/out-tsc/src/list/ShortcutList.js +1 -1
  14. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  15. package/out-tsc/src/live/ContactChat.js +12 -321
  16. package/out-tsc/src/live/ContactChat.js.map +1 -1
  17. package/out-tsc/src/simulator/Simulator.js +428 -571
  18. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  19. package/out-tsc/test/temba-simulator.test.js +51 -32
  20. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  21. package/package.json +1 -1
  22. package/screenshots/truth/contacts/chat-failure.png +0 -0
  23. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  24. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  25. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  26. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  27. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  28. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  29. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  30. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  31. package/screenshots/truth/simulator/after-reset.png +0 -0
  32. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  33. package/screenshots/truth/simulator/context-expanded.png +0 -0
  34. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  35. package/screenshots/truth/simulator/event-info.png +0 -0
  36. package/screenshots/truth/simulator/image-attachment.png +0 -0
  37. package/screenshots/truth/simulator/open-initial.png +0 -0
  38. package/screenshots/truth/simulator/quick-replies.png +0 -0
  39. package/src/display/Chat.ts +123 -44
  40. package/src/events/eventRenderers.ts +527 -0
  41. package/src/flow/Editor.ts +3 -2
  42. package/src/flow/NodeEditor.ts +0 -1
  43. package/src/list/ShortcutList.ts +1 -1
  44. package/src/live/ContactChat.ts +17 -376
  45. package/src/simulator/Simulator.ts +487 -612
  46. package/test/temba-simulator.test.ts +64 -34
@@ -12,32 +12,22 @@ import {
12
12
  fetchResults,
13
13
  generateUUIDv7,
14
14
  getUrl,
15
- oxfordFn,
16
15
  postJSON,
17
16
  postUrl,
18
17
  WebResponse
19
18
  } from '../utils';
20
19
  import { ContactStoreElement } from './ContactStoreElement';
21
20
  import { Compose, ComposeValue } from '../form/Compose';
22
- import {
23
- AirtimeTransferredEvent,
24
- CallEvent,
25
- ChatStartedEvent,
26
- ContactGroupsEvent,
27
- ContactHistoryPage,
28
- ContactLanguageChangedEvent,
29
- ContactStatusChangedEvent,
30
- NameChangedEvent,
31
- OptInEvent,
32
- RunEvent,
33
- TicketEvent,
34
- UpdateFieldEvent,
35
- URNsChangedEvent
36
- } from '../events';
21
+ import { ContactHistoryPage } from '../events';
37
22
  import { Chat, MessageType, ContactEvent } from '../display/Chat';
38
23
  import { DEFAULT_AVATAR } from '../webchat/assets';
39
24
  import { UserSelect } from '../form/select/UserSelect';
40
25
  import { Select } from '../form/select/Select';
26
+ import {
27
+ renderEvent,
28
+ renderTicketAction,
29
+ renderTicketAssigneeChanged
30
+ } from '../events/eventRenderers';
41
31
 
42
32
  /*
43
33
  export const SCROLL_THRESHOLD = 100;
@@ -47,249 +37,8 @@ export const MIN_CHAT_REFRESH = 500;
47
37
  export const BODY_SNIPPET_LENGTH = 250;
48
38
  */
49
39
 
50
- export enum Events {
51
- AIRTIME_TRANSFERRED = 'airtime_transferred',
52
- BROADCAST_CREATED = 'broadcast_created',
53
- CALL_CREATED = 'call_created',
54
- CALL_MISSED = 'call_missed',
55
- CALL_RECEIVED = 'call_received',
56
- CHAT_STARTED = 'chat_started',
57
- CONTACT_FIELD_CHANGED = 'contact_field_changed',
58
- CONTACT_GROUPS_CHANGED = 'contact_groups_changed',
59
- CONTACT_LANGUAGE_CHANGED = 'contact_language_changed',
60
- CONTACT_NAME_CHANGED = 'contact_name_changed',
61
- CONTACT_STATUS_CHANGED = 'contact_status_changed',
62
- CONTACT_URNS_CHANGED = 'contact_urns_changed',
63
- IVR_CREATED = 'ivr_created',
64
- MSG_CREATED = 'msg_created',
65
- MSG_RECEIVED = 'msg_received',
66
- OPTIN_REQUESTED = 'optin_requested',
67
- OPTIN_STARTED = 'optin_started',
68
- OPTIN_STOPPED = 'optin_stopped',
69
- RUN_ENDED = 'run_ended',
70
- RUN_STARTED = 'run_started',
71
- TICKET_ASSIGNEE_CHANGED = 'ticket_assignee_changed',
72
- TICKET_CLOSED = 'ticket_closed',
73
- TICKET_NOTE_ADDED = 'ticket_note_added',
74
- TICKET_OPENED = 'ticket_opened',
75
- TICKET_REOPENED = 'ticket_reopened',
76
- TICKET_TOPIC_CHANGED = 'ticket_topic_changed'
77
- }
78
-
79
- const renderInfoList = (
80
- singular: string,
81
- plural: string,
82
- items: any[]
83
- ): TemplateResult => {
84
- if (items.length === 1) {
85
- return html`<div>${singular} <strong>${items[0].name}</strong></div>`;
86
- } else {
87
- const list = items.map((item) => item.name);
88
- if (list.length === 2) {
89
- return html`<div>
90
- ${plural} <strong>${list[0]}</strong> and <strong>${list[1]}</strong>
91
- </div>`;
92
- } else {
93
- const last = list.pop();
94
- const middle = list.map(
95
- (name, index) =>
96
- html`<strong>${name}</strong>${index < list.length - 1 ? ', ' : ''}`
97
- );
98
- return html`<div>${plural} ${middle}, and <strong>${last}</strong></div>`;
99
- }
100
- }
101
- };
102
-
103
- const renderRunEvent = (event: RunEvent): TemplateResult => {
104
- let verb = 'Started';
105
- if (event.type === Events.RUN_ENDED) {
106
- if (event.status === 'completed') {
107
- verb = 'Completed';
108
- } else if (event.status === 'expired') {
109
- verb = 'Expired from';
110
- } else {
111
- verb = 'Interrupted';
112
- }
113
- }
114
-
115
- return html`<div>
116
- ${verb}
117
- <a href="/flow/editor/${event.flow.uuid}/"
118
- ><strong>${event.flow.name}</strong></a
119
- >
120
- </div>`;
121
- };
122
-
123
- const renderChatStartedEvent = (event: ChatStartedEvent): TemplateResult => {
124
- if (event.params) {
125
- return html`<div>Chat referral</div>`;
126
- } else {
127
- return html`<div>Chat started</div>`;
128
- }
129
- };
130
-
131
- const renderUpdateEvent = (event: UpdateFieldEvent): TemplateResult => {
132
- return event.value
133
- ? html`<div>
134
- Updated <strong>${event.field.name}</strong> to
135
- <strong>${event.value.text}</strong>
136
- </div>`
137
- : html`<div>Cleared <strong>${event.field.name}</strong></div>`;
138
- };
139
-
140
- const renderNameChanged = (event: NameChangedEvent): TemplateResult => {
141
- return html`<div>
142
- Updated <strong>name</strong> to <strong>${event.name}</strong>
143
- </div>`;
144
- };
145
-
146
- const renderContactURNsChanged = (event: URNsChangedEvent): TemplateResult => {
147
- return html`<div>
148
- Updated <strong>URNs</strong> to
149
- ${oxfordFn(
150
- event.urns,
151
- (urn: string) => html`<strong>${urn.split(':')[1].split('?')[0]}</strong>`
152
- )}
153
- </div>`;
154
- };
155
-
156
- export const renderTicketAction = (
157
- event: TicketEvent,
158
- action: string
159
- ): TemplateResult => {
160
- const ticketUUID = event.ticket?.uuid || event.ticket_uuid;
161
-
162
- const actionNote = event.note
163
- ? html`<div
164
- style="width:85%; background: #fffac3; padding: 1em;margin-bottom: 1em;margin-top:1em; border: 1px solid #ffe97f;border-radius: var(--curvature);line-height: 1.2em; word-break: break-word;"
165
- >
166
- <div style="color: #8e830fff; font-size: 1em;margin-bottom:0.25em; ">
167
- <strong>${event._user ? event._user.name : 'Someone'}</strong> added a
168
- note
169
- <temba-date
170
- value=${event.created_on.toISOString()}
171
- display="relative"
172
- ></temba-date>
173
- </div>
174
- <div style="white-space: pre-wrap;">${event.note}</div>
175
- </div>`
176
- : null;
177
-
178
- if (action === 'noted') {
179
- return html`${actionNote}`;
180
- }
181
-
182
- const description = event._user
183
- ? html`<div>
184
- <strong>${event._user.name}</strong> ${action} a
185
- <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
186
- </div>`
187
- : html`<div>
188
- A
189
- <strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
190
- was <strong>${action}</strong>
191
- </div>`;
192
-
193
- return html`<div style="${actionNote ? 'margin-bottom: 1em;' : ''}">
194
- ${description}
195
- </div>
196
- ${actionNote}`;
197
- };
198
-
199
- export const renderTicketAssigneeChanged = (
200
- event: TicketEvent
201
- ): TemplateResult => {
202
- if (event._user) {
203
- if (event.assignee) {
204
- return html`<div>
205
- <strong>${event._user.name}</strong> assigned this ticket to
206
- <strong>${event.assignee.name}</strong>
207
- </div>`;
208
- } else {
209
- return html`<div>
210
- <strong>${event._user.name}</strong> unassigned this ticket
211
- </div>`;
212
- }
213
- } else {
214
- if (event.assignee) {
215
- return html`<div>
216
- This ticket was assigned to <strong>${event.assignee.name}</strong>
217
- </div>`;
218
- } else {
219
- return html`<div>This ticket was unassigned</div>`;
220
- }
221
- }
222
- };
223
-
224
- export const renderTicketOpened = (event: TicketEvent): TemplateResult => {
225
- return html`<div>${event.ticket.topic.name} ticket was opened</div>`;
226
- };
227
-
228
- export const renderContactGroupsEvent = (
229
- event: ContactGroupsEvent
230
- ): TemplateResult => {
231
- const groupsEvent = event as ContactGroupsEvent;
232
- if (groupsEvent.groups_added) {
233
- return renderInfoList(
234
- 'Added to group',
235
- 'Added to groups',
236
- groupsEvent.groups_added
237
- );
238
- } else if (groupsEvent.groups_removed) {
239
- return renderInfoList(
240
- 'Removed from group',
241
- 'Removed from groups',
242
- groupsEvent.groups_removed
243
- );
244
- }
245
- };
246
-
247
- export const renderAirtimeTransferredEvent = (
248
- event: AirtimeTransferredEvent
249
- ): TemplateResult => {
250
- if (parseFloat(event.amount) === 0) {
251
- return html`<div>Airtime transfer failed</div>`;
252
- }
253
- return html`<div>
254
- Transferred <strong>${event.amount}</strong> ${event.currency} of airtime
255
- </div>`;
256
- };
257
-
258
- export const renderContactLanguageChangedEvent = (
259
- event: ContactLanguageChangedEvent
260
- ): TemplateResult => {
261
- return html`<div>
262
- Language updated to <strong>${event.language}</strong>
263
- </div>`;
264
- };
265
-
266
- export const renderContactStatusChangedEvent = (
267
- event: ContactStatusChangedEvent
268
- ): TemplateResult => {
269
- return html`<div>Status updated to <strong>${event.status}</strong></div>`;
270
- };
271
-
272
- export const renderCallEvent = (event: CallEvent): TemplateResult => {
273
- if (event.type === Events.CALL_CREATED) {
274
- return html`<div>Call started</div>`;
275
- } else if (event.type === Events.CALL_MISSED) {
276
- return html`<div>Call missed</div>`;
277
- } else if (event.type === Events.CALL_RECEIVED) {
278
- return html`<div>Call answered</div>`;
279
- }
280
- };
281
-
282
- export const renderOptInEvent = (event: OptInEvent): TemplateResult => {
283
- if (event.type === Events.OPTIN_REQUESTED) {
284
- return html`<div>
285
- Requested opt-in for <strong>${event.optin.name}</strong>
286
- </div>`;
287
- } else if (event.type === Events.OPTIN_STARTED) {
288
- return html`<div>Opted in to <strong>${event.optin.name}</strong></div>`;
289
- } else if (event.type === Events.OPTIN_STOPPED) {
290
- return html`<div>Opted out of <strong>${event.optin.name}</strong></div>`;
291
- }
292
- };
40
+ // re-export for backwards compatibility
41
+ export { renderTicketAction, renderTicketAssigneeChanged };
293
42
 
294
43
  export class ContactChat extends ContactStoreElement {
295
44
  public static get styles() {
@@ -711,123 +460,14 @@ export class ContactChat extends ContactStoreElement {
711
460
  }
712
461
 
713
462
  public prerender(event: ContactEvent) {
714
- switch (event.type) {
715
- case Events.AIRTIME_TRANSFERRED:
716
- event._rendered = {
717
- html: renderAirtimeTransferredEvent(event as AirtimeTransferredEvent),
718
- type: MessageType.Inline
719
- };
720
- break;
721
- case Events.CALL_CREATED:
722
- case Events.CALL_MISSED:
723
- case Events.CALL_RECEIVED:
724
- event._rendered = {
725
- html: renderCallEvent(event as CallEvent),
726
- type: MessageType.Inline
727
- };
728
- break;
729
- case Events.CHAT_STARTED:
730
- event._rendered = {
731
- html: renderChatStartedEvent(event as ChatStartedEvent),
732
- type: MessageType.Inline
733
- };
734
- break;
735
- case Events.CONTACT_FIELD_CHANGED:
736
- event._rendered = {
737
- html: renderUpdateEvent(event as UpdateFieldEvent),
738
- type: MessageType.Inline
739
- };
740
- break;
741
- case Events.CONTACT_GROUPS_CHANGED:
742
- event._rendered = {
743
- html: renderContactGroupsEvent(event as ContactGroupsEvent),
744
- type: MessageType.Inline
745
- };
746
- break;
747
- case Events.CONTACT_LANGUAGE_CHANGED:
748
- event._rendered = {
749
- html: renderContactLanguageChangedEvent(
750
- event as ContactLanguageChangedEvent
751
- ),
752
- type: MessageType.Inline
753
- };
754
- break;
755
- case Events.CONTACT_NAME_CHANGED:
756
- event._rendered = {
757
- html: renderNameChanged(event as NameChangedEvent),
758
- type: MessageType.Inline
759
- };
760
- break;
761
- case Events.CONTACT_STATUS_CHANGED:
762
- event._rendered = {
763
- html: renderContactStatusChangedEvent(
764
- event as ContactStatusChangedEvent
765
- ),
766
- type: MessageType.Inline
767
- };
768
- break;
769
- case Events.CONTACT_URNS_CHANGED:
770
- event._rendered = {
771
- html: renderContactURNsChanged(event as URNsChangedEvent),
772
- type: MessageType.Inline
773
- };
774
- break;
775
- case Events.OPTIN_REQUESTED:
776
- case Events.OPTIN_STARTED:
777
- case Events.OPTIN_STOPPED:
778
- event._rendered = {
779
- html: renderOptInEvent(event as OptInEvent),
780
- type: MessageType.Inline
781
- };
782
- break;
783
- case Events.RUN_STARTED:
784
- case Events.RUN_ENDED:
785
- event._rendered = {
786
- html: renderRunEvent(event as RunEvent),
787
- type: MessageType.Inline
788
- };
789
- break;
790
- case Events.TICKET_ASSIGNEE_CHANGED:
791
- event._rendered = {
792
- html: renderTicketAssigneeChanged(event as TicketEvent),
793
- type: MessageType.Inline
794
- };
795
- break;
796
- case Events.TICKET_CLOSED:
797
- event._rendered = {
798
- html: renderTicketAction(event as TicketEvent, 'closed'),
799
- type: MessageType.Inline
800
- };
801
- break;
802
- case Events.TICKET_OPENED:
803
- event._rendered = {
804
- html: renderTicketAction(event as TicketEvent, 'opened'),
805
- type: MessageType.Inline
806
- };
807
- break;
808
- case Events.TICKET_NOTE_ADDED:
809
- event._rendered = {
810
- html: renderTicketAction(event as TicketEvent, 'noted'),
811
- type: MessageType.Inline
812
- };
813
- break;
814
- case Events.TICKET_REOPENED:
815
- event._rendered = {
816
- html: renderTicketAction(event as TicketEvent, 'reopened'),
817
- type: MessageType.Inline
818
- };
819
- break;
820
- case Events.TICKET_TOPIC_CHANGED:
821
- event._rendered = {
822
- html: html`<div>
823
- Topic changed to
824
- <strong>${(event as TicketEvent).topic.name}</strong>
825
- </div>`,
826
- type: MessageType.Inline
827
- };
828
- break;
829
- default:
830
- // console.error('Unknown event type', event);
463
+ // use the unified renderEvent function with isSimulation = false
464
+ const rendered = renderEvent(event, false);
465
+
466
+ if (rendered) {
467
+ event._rendered = {
468
+ html: rendered,
469
+ type: MessageType.Inline
470
+ };
831
471
  }
832
472
  }
833
473
 
@@ -1116,6 +756,7 @@ export class ContactChat extends ContactStoreElement {
1116
756
  @temba-fetch-complete=${this.fetchComplete}
1117
757
  avatar=${this.avatar}
1118
758
  agent
759
+ avatars
1119
760
  ?hasFooter=${inFlow}
1120
761
  .showMessageLogsAfter=${this.showMessageLogsAfter}
1121
762
  >