@qualified-salesforce/web 0.0.1-oidc-bootstrap → 0.1.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 (97) hide show
  1. package/CAPABILITY-REFERENCE.md +44 -0
  2. package/CHANGELOG.md +75 -0
  3. package/constants.generated.js +30 -2
  4. package/css/widget/multimodal_v2/index.split-css.panda.css +265 -47
  5. package/package.json +17 -3
  6. package/packs/widget/api/docking/viewport_lock.js +93 -2
  7. package/packs/widget/esm/index.d.ts +134 -0
  8. package/packs/widget/esm/index.js +18 -2
  9. package/packs/widget/esm/qualified.css +265 -47
  10. package/src/graphql-types.js +74 -1
  11. package/src/url.js +4 -1
  12. package/vendor/actioncable/connection.js +40 -3
  13. package/vendor/actioncable/connection_monitor.js +8 -0
  14. package/vendor/actioncable/consumer.js +7 -1
  15. package/vendor/actioncable/index.js +5 -2
  16. package/vendor/actioncable/sse_socket.js +262 -0
  17. package/widget/analytics.js +8 -0
  18. package/widget/api_handler/base_api_handler.js +24 -0
  19. package/widget/booker_outcome_tracker.js +58 -20
  20. package/widget/capabilities.js +190 -0
  21. package/widget/channel.js +38 -6
  22. package/widget/constants.generated.js +2 -1
  23. package/widget/multimodal_v2/components/AppLayout.js +1 -1
  24. package/widget/multimodal_v2/components/app/Foreground.js +10 -9
  25. package/widget/multimodal_v2/components/app/Input.js +8 -3
  26. package/widget/multimodal_v2/components/app/LightboxExperience.js +17 -3
  27. package/widget/multimodal_v2/components/app/Main.js +87 -31
  28. package/widget/multimodal_v2/components/app/MessengerButton.js +18 -1
  29. package/widget/multimodal_v2/components/chat/ChatInputForm.js +8 -6
  30. package/widget/multimodal_v2/components/chat/timeline/CalendarEventPickRenderer.js +2 -2
  31. package/widget/multimodal_v2/components/chat/timeline/PresentDeckRenderer.js +346 -170
  32. package/widget/multimodal_v2/components/chat/timeline/SystemRenderer.js +4 -7
  33. package/widget/multimodal_v2/components/decks/index.js +506 -52
  34. package/widget/multimodal_v2/components/helpers/content_click_payloads.js +67 -1
  35. package/widget/multimodal_v2/components/lightbox_gather/index.js +4 -4
  36. package/widget/multimodal_v2/components/meetings/MeetingBookerEntry.js +8 -1
  37. package/widget/multimodal_v2/components/meetings/MeetingOfferEmailStep.js +1 -1
  38. package/widget/multimodal_v2/components/meetings/MeetingSummaryRail.js +1 -1
  39. package/widget/multimodal_v2/components/meetings/MeetingTimeSelector.js +1 -1
  40. package/widget/multimodal_v2/components/multimodal/Controls.js +11 -3
  41. package/widget/multimodal_v2/components/multimodal/useMultimodal.js +19 -7
  42. package/widget/multimodal_v2/components/presentations/index.js +61 -0
  43. package/widget/multimodal_v2/constants.js +1 -1
  44. package/widget/multimodal_v2/images/icons.js +17 -1
  45. package/widget/multimodal_v2/index.panda.js +2 -2
  46. package/widget/multimodal_v2/locales/cs.js +8 -1
  47. package/widget/multimodal_v2/locales/da.js +8 -1
  48. package/widget/multimodal_v2/locales/de.js +8 -1
  49. package/widget/multimodal_v2/locales/en.js +8 -1
  50. package/widget/multimodal_v2/locales/en_AU.js +8 -1
  51. package/widget/multimodal_v2/locales/en_GB.js +8 -1
  52. package/widget/multimodal_v2/locales/es.js +8 -1
  53. package/widget/multimodal_v2/locales/es_LA.js +8 -1
  54. package/widget/multimodal_v2/locales/fi.js +8 -1
  55. package/widget/multimodal_v2/locales/fr.js +8 -1
  56. package/widget/multimodal_v2/locales/fr_CA.js +8 -1
  57. package/widget/multimodal_v2/locales/id.js +8 -1
  58. package/widget/multimodal_v2/locales/it.js +8 -1
  59. package/widget/multimodal_v2/locales/ja.js +8 -1
  60. package/widget/multimodal_v2/locales/ko.js +8 -1
  61. package/widget/multimodal_v2/locales/nb.js +8 -1
  62. package/widget/multimodal_v2/locales/nl.js +8 -1
  63. package/widget/multimodal_v2/locales/pl.js +8 -1
  64. package/widget/multimodal_v2/locales/pt_BR.js +8 -1
  65. package/widget/multimodal_v2/locales/ru.js +8 -1
  66. package/widget/multimodal_v2/locales/sv.js +8 -1
  67. package/widget/multimodal_v2/locales/th.js +8 -1
  68. package/widget/multimodal_v2/locales/tr.js +8 -1
  69. package/widget/multimodal_v2/locales/vi.js +8 -1
  70. package/widget/multimodal_v2/locales/zh_CN.js +8 -1
  71. package/widget/multimodal_v2/locales/zh_TW.js +8 -1
  72. package/widget/multimodal_v2/multimodal.js +5 -1
  73. package/widget/multimodal_v2/multimodal_channel.js +13 -8
  74. package/widget/multimodal_v2/signals/messages_signal.js +4 -0
  75. package/widget/multimodal_v2/stores/ai_conversation_store.js +6 -2
  76. package/widget/multimodal_v2/stores/artifact_store.js +352 -27
  77. package/widget/multimodal_v2/stores/call_store.js +40 -1
  78. package/widget/multimodal_v2/stores/call_ui_adapter.js +1 -1
  79. package/widget/multimodal_v2/stores/decks_store.js +405 -39
  80. package/widget/multimodal_v2/stores/meeting_store.js +15 -10
  81. package/widget/multimodal_v2/stores/presentation_ga_adapter.js +64 -0
  82. package/widget/multimodal_v2/stores/presentation_telemetry_store.js +556 -0
  83. package/widget/multimodal_v2/stores/presentations_store.js +1 -0
  84. package/widget/multimodal_v2/stores/root_store.js +157 -268
  85. package/widget/multimodal_v2/stores/screen_share_store.js +41 -21
  86. package/widget/multimodal_v2/stores/translation_store.js +9 -0
  87. package/widget/multimodal_v2/stores/ui_store.js +7 -7
  88. package/widget/multimodal_v2/stores/website_navigation_store.js +3 -1
  89. package/widget/multimodal_v2/text_turn_response_tracker.js +419 -0
  90. package/widget/multimodal_v2/types.js +21 -1
  91. package/widget/multimodal_v2/utils/locale.js +2 -1
  92. package/widget/multimodal_v2/utils/signals.js +35 -4
  93. package/widget/pardot_visitor.js +7 -1
  94. package/widget/server-logger.js +97 -2
  95. package/widget/tracker.js +69 -21
  96. package/widget/widget/page_mirror_factory.js +9 -1
  97. package/widget/widget/widget.js +29 -4
@@ -28,6 +28,72 @@
28
28
  }
29
29
  };
30
30
  }
31
+ /**
32
+ * Canonical presentation-type labels, mirroring the server-side mapping in
33
+ * multimodal_sql_fragments.rb. Codegen only exports that mapping to the admin
34
+ * bundle, so the widget keeps this typed equivalent.
35
+ */ const PRESENTATION_TYPE_LABELS = {
36
+ present_deck: "Slide Deck",
37
+ present_video: "Product Demo",
38
+ present_content: "Image"
39
+ };
40
+ /**
41
+ * Payload for custEv_chatPresentationStart. Fires once when a presentation is
42
+ * initiated and becomes visible to the visitor.
43
+ */ function buildCustEvChatPresentationStartPayload(param) {
44
+ let { conversationId, formJoinIDUnique = "", title, type, totalSlides, triggeredBy } = param;
45
+ return {
46
+ event: "custEv_chatPresentationStart",
47
+ chatExperience: "qualified",
48
+ nameCaptureCampaignId: "",
49
+ formJoinIDUnique,
50
+ conversationId,
51
+ presentation: {
52
+ title,
53
+ type,
54
+ totalSlides,
55
+ triggeredBy
56
+ }
57
+ };
58
+ }
59
+ /**
60
+ * Payload for custEv_chatPresentationSlide. Fires once each time a slide becomes
61
+ * visible — whether Piper advances the deck or the visitor navigates manually.
62
+ */ function buildCustEvChatPresentationSlidePayload(param) {
63
+ let { conversationId, formJoinIDUnique = "", title, type, slideTitle, slidePosition } = param;
64
+ return {
65
+ event: "custEv_chatPresentationSlide",
66
+ chatExperience: "qualified",
67
+ nameCaptureCampaignId: "",
68
+ formJoinIDUnique,
69
+ conversationId,
70
+ presentation: {
71
+ title,
72
+ type,
73
+ slideTitle,
74
+ slidePosition
75
+ }
76
+ };
77
+ }
78
+ /**
79
+ * Payload for custEv_chatPresentationEnd. Fires once when a live presentation
80
+ * reaches any terminal lifecycle boundary.
81
+ */ function buildCustEvChatPresentationEndPayload(param) {
82
+ let { conversationId, formJoinIDUnique = "", title, type, slidesShown, secondsWatched } = param;
83
+ return {
84
+ event: "custEv_chatPresentationEnd",
85
+ chatExperience: "qualified",
86
+ nameCaptureCampaignId: "",
87
+ formJoinIDUnique,
88
+ conversationId,
89
+ presentation: {
90
+ title,
91
+ type,
92
+ slidesShown,
93
+ secondsWatched
94
+ }
95
+ };
96
+ }
31
97
  /** Payload for custEv_contentClick when a link in the widget is clicked (e.g. in a message body). */ function buildCustEvContentClickPayloadForLink(url, moduleName, clickedLinkText, conversationId) {
32
98
  if (conversationId === void 0) conversationId = "";
33
99
  return {
@@ -55,4 +121,4 @@
55
121
  };
56
122
  }
57
123
 
58
- export { buildCustEvChatViewPayload, buildCustEvContentClickPayloadForLink, buildCustEvContentImpressionPayload };
124
+ export { PRESENTATION_TYPE_LABELS, buildCustEvChatPresentationEndPayload, buildCustEvChatPresentationSlidePayload, buildCustEvChatPresentationStartPayload, buildCustEvChatViewPayload, buildCustEvContentClickPayloadForLink, buildCustEvContentImpressionPayload };
@@ -43,7 +43,7 @@ function _extends() {
43
43
  const LightboxGather = (param)=>{
44
44
  let { rootStore } = param;
45
45
  var _message_inputFields;
46
- const { effectiveLocale, t } = rootStore.translationStore;
46
+ const { effectiveIntlLocale, t } = rootStore.translationStore;
47
47
  const fieldValues = signals_module_useSignal({});
48
48
  const fieldErrors = signals_module_useSignal({});
49
49
  const handleFieldChange = hooks_module_q((index, value)=>{
@@ -173,7 +173,7 @@ const LightboxGather = (param)=>{
173
173
  "data-component-name": "LightboxGather.CalendarColumn",
174
174
  children: /*#__PURE__*/ jsxRuntime_module_u(InertCalendarPlaceholder, {
175
175
  t: t,
176
- effectiveLocale: effectiveLocale.value
176
+ locale: effectiveIntlLocale.value
177
177
  })
178
178
  })
179
179
  ]
@@ -347,7 +347,7 @@ const previewHasPrev = signals_core_module_d(false);
347
347
  const previewHasNext = signals_core_module_d(false);
348
348
  const FAKE_SLOTS = generateFakeTimeSlots();
349
349
  const InertCalendarPlaceholder = (param)=>{
350
- let { t, effectiveLocale } = param;
350
+ let { t, locale } = param;
351
351
  const dayOfWeek = today.getDay();
352
352
  const startOfWeek = new Date(today);
353
353
  startOfWeek.setDate(today.getDate() - dayOfWeek);
@@ -378,7 +378,7 @@ const InertCalendarPlaceholder = (param)=>{
378
378
  availableDateStrings: previewAvailableDates,
379
379
  selectedDateString: previewSelectedDate,
380
380
  onSelectDate: ()=>{},
381
- locale: effectiveLocale,
381
+ locale: locale,
382
382
  openDatePickerLabel: t("meetings.openDatePicker")
383
383
  }),
384
384
  /*#__PURE__*/ jsxRuntime_module_u(DayStripRow, {
@@ -1,4 +1,5 @@
1
1
  import { jsxRuntime_module_u } from "../../../../js/multimodal_v2.js";
2
+ import { getSentryClient } from "../../../widget/sentry.js";
2
3
  import { CalendarResumeIcon } from "../../images/icons.js";
3
4
  import "../../styled-system/jsx/index.js";
4
5
  import { styled } from "../../styled-system/jsx/factory.js";
@@ -6,6 +7,7 @@ import { styled } from "../../styled-system/jsx/factory.js";
6
7
 
7
8
  /** @jsxImportSource preact */
8
9
 
10
+
9
11
  /**
10
12
  * The meeting booker's presence in the chat timeline: a placeholder card that
11
13
  * opens the booker in the artifact overlay, which is the only surface the booker
@@ -20,7 +22,12 @@ import { styled } from "../../styled-system/jsx/factory.js";
20
22
  let { rootStore, message } = param;
21
23
  const { t } = rootStore.translationStore;
22
24
  const handleClick = ()=>{
23
- void rootStore.toggleMeetingBookerArtifact(message);
25
+ rootStore.toggleMeetingBookerArtifact(message).catch((error)=>{
26
+ var _getSentryClient;
27
+ (_getSentryClient = getSentryClient({
28
+ project: "multimodal"
29
+ })) == null ? void 0 : _getSentryClient.captureException(error);
30
+ });
24
31
  };
25
32
  return /*#__PURE__*/ jsxRuntime_module_u(CardButton, {
26
33
  "data-component-name": "MeetingBookerEntry.CardButton",
@@ -128,7 +128,7 @@ function useEmailStep(param) {
128
128
  // Read infrequent values at the top (these rarely change)
129
129
  const selectedTimeSlot = meetingStore.selectedTimeSlot.value;
130
130
  const timeZone = meetingStore.timeZone.value;
131
- const locale = meetingStore.rootStore.translationStore.effectiveLocale.value;
131
+ const locale = meetingStore.rootStore.translationStore.effectiveIntlLocale.value;
132
132
  // Handlers - simple wrappers around store actions
133
133
  const handleEmailChange = (e)=>{
134
134
  const target = e.currentTarget;
@@ -73,7 +73,7 @@ import { styled } from "../../styled-system/jsx/factory.js";
73
73
  const timeZone = meetingStore.timeZone.value;
74
74
  const timeZoneLabel = (_ref2 = (_AVAILABLE_TIME_ZONES_find = AVAILABLE_TIME_ZONES.find((tz)=>tz.value === timeZone)) == null ? void 0 : _AVAILABLE_TIME_ZONES_find.label) != null ? _ref2 : timeZone;
75
75
  const slot = meetingStore.selectedTimeSlot.value;
76
- const locale = meetingStore.rootStore.translationStore.effectiveLocale.value;
76
+ const locale = meetingStore.rootStore.translationStore.effectiveIntlLocale.value;
77
77
  const meta = /*#__PURE__*/ jsxRuntime_module_u(Meta, {
78
78
  "data-component-name": "MeetingSummaryRail.Meta",
79
79
  children: [
@@ -269,7 +269,7 @@ function useMeetingTimeSelector(param) {
269
269
  displayedTimeSlotItems: meetingStore.displayedTimeSlotItems,
270
270
  slotPageSize: meetingStore.slotPageSize,
271
271
  layout,
272
- locale: meetingStore.rootStore.translationStore.effectiveLocale
272
+ locale: meetingStore.rootStore.translationStore.effectiveIntlLocale
273
273
  },
274
274
  handlers: {
275
275
  handleSelectTimeSlot,
@@ -20,9 +20,17 @@ import { styled } from "../../styled-system/jsx/factory.js";
20
20
 
21
21
 
22
22
  const Controls = (param)=>{
23
- let { connectingStatus, conversationMode, mic, maximize, minimizeToCorner, endCall } = param;
24
- return /*#__PURE__*/ jsxRuntime_module_u(Container, {
23
+ let { connectingStatus, conversationMode, mic, maximize, minimizeToCorner, endCall, avatarProvider } = param;
24
+ var _ref;
25
+ return(// "Video Mode"/"Voice Mode" buttons regardless of the active mode (only
26
+ // aria-pressed/styling differ), so E2E can't tell modes apart by button
27
+ // visibility/text — it needs this explicit signal instead. On the
28
+ // Container (not gated behind conversationMode.visible) so it reads
29
+ // correctly even for a team where the toggle itself is hidden.
30
+ /*#__PURE__*/ jsxRuntime_module_u(Container, {
25
31
  "data-component-name": "Controls.Container",
32
+ "data-avatar-provider": (_ref = avatarProvider == null ? void 0 : avatarProvider.value) != null ? _ref : undefined,
33
+ "data-conversation-mode": conversationMode.currentMode.value,
26
34
  children: [
27
35
  /*#__PURE__*/ jsxRuntime_module_u(utils_module_u, {
28
36
  when: conversationMode.visible,
@@ -134,7 +142,7 @@ const Controls = (param)=>{
134
142
  })
135
143
  })
136
144
  ]
137
- });
145
+ }));
138
146
  };
139
147
  const VIDEO_MODE_OPTIONS = [
140
148
  {
@@ -72,11 +72,14 @@ function useVariant(rootStore, adapter) {
72
72
  const maximizedState = rootStore.maximizedState.value;
73
73
  const isMaximized = maximizedState === types_MaximizedState.MULTIMODAL && isOpened;
74
74
  if (isMaximized) return "maximized";
75
- const isMobileArtifact = isMobile.value && maximizedState === types_MaximizedState.ARTIFACT;
76
- if (isMobileArtifact) return isOpened ? "pill" : "compact";
77
75
  if (rootStore.displayMode.minimized.value && rootStore.conversationMode.video.value) return "compact";
78
76
  const adapterState = adapter.value.state;
79
- const isVideoInCall = isMobile.value && rootStore.conversationMode.video.value && (adapterState.isInCall.value || adapterState.isVideoLoading.value);
77
+ // A mobile video call takes over the screen -- unless the artifact is already
78
+ // doing that. Then the call floats in the bottom-right corner over it, exactly
79
+ // as it does in the closed state, and it gets there by resolving what the
80
+ // closed state resolves: the fall-through below, which is the compact tile for
81
+ // video and the pill for voice.
82
+ const isVideoInCall = isMobile.value && rootStore.conversationMode.video.value && maximizedState !== types_MaximizedState.ARTIFACT && (adapterState.isInCall.value || adapterState.isVideoLoading.value);
80
83
  if (isVideoInCall) return "maximized";
81
84
  if ((_rootStore_postCallSurveyStore_value = rootStore.postCallSurveyStore.value) == null ? void 0 : _rootStore_postCallSurveyStore_value.isActive.value) return "compact";
82
85
  if (!isMaximized && (isVoiceMode || userHasIgnoredMultimodal && isOpened)) return "pill";
@@ -278,7 +281,13 @@ function useControlsConfig(rootStore, adapter, variant, visibility) {
278
281
  return rootStore.conversationMode.video.value ? "compactInside" : "compactOutside";
279
282
  });
280
283
  // Computed signals for controls props
281
- const conversationModeVisible = useComputed(()=>rootStore.allowVoiceVideoToggle.value && !adapterState.isBlurredVideoActive.value && rootStore.maximizedState.value !== types_MaximizedState.MULTIMODAL && !rootStore.displayMode.minimized.value);
284
+ // A call taken with the artifact open on a phone is a small panel floating in the
285
+ // bottom-right corner over it (see the artifact rules in Main). The toggle is a
286
+ // two-option pill that would take most of that panel's width and change its shape
287
+ // underneath the visitor mid-call, so it stands down there for the same reason it
288
+ // stands down for a fullscreen call. The closed state parks a call in that corner
289
+ // too and needs no term of its own: the `minimized` check below already covers it.
290
+ const conversationModeVisible = useComputed(()=>rootStore.allowVoiceVideoToggle.value && !adapterState.isBlurredVideoActive.value && rootStore.maximizedState.value !== types_MaximizedState.MULTIMODAL && !(isMobile.value && rootStore.maximizedState.value === types_MaximizedState.ARTIFACT && (adapterState.isInCall.value || adapterState.isVideoLoading.value)) && !rootStore.displayMode.minimized.value);
282
291
  const micIsOn = useComputed(()=>!adapterState.isMicMuted.value);
283
292
  const micHasDevicesAvailable = useComputed(()=>adapterState.microphoneDevices.value.length > 0);
284
293
  const micDevices = useComputed(()=>adapterState.microphoneDevices.value.map((d)=>({
@@ -318,7 +327,7 @@ function useControlsConfig(rootStore, adapter, variant, visibility) {
318
327
  if (rootStore.displayMode.minimized.value) {
319
328
  rootStore.setOpenedDisplayMode();
320
329
  }
321
- rootStore.setMaximizedState(types_MaximizedState.MULTIMODAL);
330
+ rootStore.endPresentationAndSetMaximizedState(types_MaximizedState.MULTIMODAL);
322
331
  }
323
332
  }
324
333
  },
@@ -339,10 +348,13 @@ function useControlsConfig(rootStore, adapter, variant, visibility) {
339
348
  endCall: {
340
349
  visible: endCallVisible,
341
350
  onClick: ()=>{
342
- rootStore.clearMaximizedState();
351
+ rootStore.endPresentationAndClearMaximizedState({
352
+ reason: "call_ended"
353
+ });
343
354
  actions.endCall();
344
355
  }
345
- }
356
+ },
357
+ avatarProvider: rootStore.activeAvatarProvider
346
358
  };
347
359
  return {
348
360
  visible: showControls,
@@ -8,6 +8,20 @@ import { styled } from "../../styled-system/jsx/factory.js";
8
8
 
9
9
  /* provided dependency */ var presentations_setInterval = __webpack_require__(68034).LK;
10
10
  /* provided dependency */ var presentations_clearInterval = __webpack_require__(68034).wm;
11
+ function _extends() {
12
+ _extends = Object.assign || function(target) {
13
+ for(var i = 1; i < arguments.length; i++){
14
+ var source = arguments[i];
15
+ for(var key in source){
16
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
17
+ target[key] = source[key];
18
+ }
19
+ }
20
+ }
21
+ return target;
22
+ };
23
+ return _extends.apply(this, arguments);
24
+ }
11
25
 
12
26
  /** @jsxImportSource preact */
13
27
 
@@ -165,6 +179,7 @@ function usePresentation(param) {
165
179
  const isNewClip = video.dataset.clipKey !== clipKey;
166
180
  if (isNewClip) {
167
181
  video.dataset.clipKey = clipKey;
182
+ delete video.dataset.completedClipKey;
168
183
  // Start at the first segment. segIndex tracks which segment is playing so timeupdate can chain
169
184
  // to the next one at each segment's end.
170
185
  video.dataset.segIndex = "0";
@@ -203,6 +218,44 @@ function usePresentation(param) {
203
218
  video.addEventListener("seeked", handleReady);
204
219
  video.addEventListener("loadeddata", handleReady);
205
220
  video.addEventListener("playing", handleReady);
221
+ const reportPlaybackState = (overrides)=>{
222
+ if (overrides === void 0) overrides = {};
223
+ rootStore.presentationTelemetryStore.onDemoPlaybackState(pres.messageId, _extends({
224
+ playing: !video.paused,
225
+ buffering: !videoReady.peek(),
226
+ seeking: video.seeking
227
+ }, overrides));
228
+ };
229
+ const handlePlaying = ()=>reportPlaybackState({
230
+ playing: true,
231
+ buffering: false
232
+ });
233
+ const handlePause = ()=>reportPlaybackState({
234
+ playing: false
235
+ });
236
+ const handleWaiting = ()=>reportPlaybackState({
237
+ playing: false,
238
+ buffering: true
239
+ });
240
+ const handleSeeking = ()=>reportPlaybackState({
241
+ playing: false,
242
+ seeking: true
243
+ });
244
+ const handleSeeked = ()=>reportPlaybackState({
245
+ seeking: false
246
+ });
247
+ video.addEventListener("playing", handlePlaying);
248
+ video.addEventListener("pause", handlePause);
249
+ video.addEventListener("waiting", handleWaiting);
250
+ video.addEventListener("seeking", handleSeeking);
251
+ video.addEventListener("seeked", handleSeeked);
252
+ const reportNaturalCompletion = ()=>{
253
+ const finalSegment = segments.at(-1);
254
+ if (!finalSegment || !video.ended && video.currentTime < finalSegment.end) return;
255
+ if (video.dataset.completedClipKey === clipKey) return;
256
+ video.dataset.completedClipKey = clipKey;
257
+ rootStore.presentationTelemetryStore.onDemoNaturalCompletion(pres.messageId);
258
+ };
206
259
  const handleTimeUpdate = ()=>{
207
260
  var _video_dataset_segIndex;
208
261
  // Publish the position so an interruption can tell the agent where it paused.
@@ -219,10 +272,12 @@ function usePresentation(param) {
219
272
  video.currentTime = nextSegment.start;
220
273
  } else if (!video.paused) {
221
274
  video.pause();
275
+ reportNaturalCompletion();
222
276
  }
223
277
  }
224
278
  };
225
279
  video.addEventListener("timeupdate", handleTimeUpdate);
280
+ video.addEventListener("ended", reportNaturalCompletion);
226
281
  // The visitor barged in — pause and freeze the frame. Otherwise keep playing.
227
282
  if (stopped) {
228
283
  if (!video.paused) video.pause();
@@ -239,9 +294,15 @@ function usePresentation(param) {
239
294
  }
240
295
  return ()=>{
241
296
  video.removeEventListener("timeupdate", handleTimeUpdate);
297
+ video.removeEventListener("ended", reportNaturalCompletion);
242
298
  video.removeEventListener("seeked", handleReady);
243
299
  video.removeEventListener("loadeddata", handleReady);
244
300
  video.removeEventListener("playing", handleReady);
301
+ video.removeEventListener("playing", handlePlaying);
302
+ video.removeEventListener("pause", handlePause);
303
+ video.removeEventListener("waiting", handleWaiting);
304
+ video.removeEventListener("seeking", handleSeeking);
305
+ video.removeEventListener("seeked", handleSeeked);
245
306
  if (captureTimer !== null) presentations_clearInterval(captureTimer);
246
307
  };
247
308
  });
@@ -52,7 +52,7 @@ const WIDGET_VISIBLE_ATTRIBUTE = "q-widget-visible";
52
52
  const MULTIMODAL_HOST_Z_INDEX = {
53
53
  docked: 2147483640,
54
54
  minimized: 2147483642,
55
- expanded: 2147483646,
55
+ expanded: 2147483647,
56
56
  maximized: 2147483647
57
57
  };
58
58
  // Mirrors backend `Message::Text::TEXT_SIZE_LIMIT` (app/models/message/text.rb), which
@@ -29,6 +29,22 @@ const PlayIcon = ()=>/*#__PURE__*/ jsxRuntime_module_u("svg", {
29
29
  d: "M5 4.13C5 3.35 5.84 2.86 6.51 3.26L13.04 7.13C13.69 7.52 13.69 8.48 13.04 8.87L6.51 12.74C5.84 13.14 5 12.65 5 11.87V4.13Z"
30
30
  })
31
31
  });
32
+ /**
33
+ * Stacked lines, for the "N slides" badge on a deck's timeline card.
34
+ */ const SlidesIcon = ()=>/*#__PURE__*/ jsxRuntime_module_u("svg", {
35
+ xmlns: "http://www.w3.org/2000/svg",
36
+ width: "12",
37
+ height: "12",
38
+ viewBox: "0 0 12 12",
39
+ fill: "none",
40
+ "aria-hidden": "true",
41
+ children: /*#__PURE__*/ jsxRuntime_module_u("path", {
42
+ d: "M1.5 2.5H10.5M1.5 6H8M1.5 9.5H6",
43
+ stroke: "currentColor",
44
+ strokeWidth: "1.5",
45
+ strokeLinecap: "round"
46
+ })
47
+ });
32
48
  /**
33
49
  * Microphone icon used in the "Speak now" button overlay on video preview.
34
50
  */ const SpeakNowMicIcon = ()=>/*#__PURE__*/ jsxRuntime_module_u("svg", {
@@ -579,4 +595,4 @@ var icons_CloseIcon = /* unused export */ undefined;
579
595
  var icons_DoubleChatIcon = /* unused export */ undefined;
580
596
  var icons_PinIcon = /* unused export */ undefined;
581
597
  var icons_SoundwaveIcon = /* unused export */ undefined;
582
- export { AlertIcon, ArrowRightIcon, CalendarIcon, CalendarResumeIcon, ChatBubbleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon, ClockIcon, CrossIcon, CursorIcon, ExpandIcon, GlobeIcon, LeftArrowIcon, MicIcon, MicOffIcon, MinimizeIcon, PinnedIcon, PipIcon, PlayIcon, RightArrowIcon, RotatePhoneIcon, SelectChevronIcon, SpeakNowMicIcon, SpinnerIcon, ThumbsDownIcon, ThumbsUpIcon, UpCaretIcon, WarningIcon, icons_CameraOffIcon as CameraOffIcon, icons_CameraOnIcon as CameraOnIcon, icons_CheckmarkIcon as CheckmarkIcon, icons_ChevronIcon as ChevronIcon, icons_CloseIcon as CloseIcon, icons_DoubleChatIcon as DoubleChatIcon, icons_PinIcon as PinIcon, icons_SoundwaveIcon as SoundwaveIcon };
598
+ export { AlertIcon, ArrowRightIcon, CalendarIcon, CalendarResumeIcon, ChatBubbleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon, ClockIcon, CrossIcon, CursorIcon, ExpandIcon, GlobeIcon, LeftArrowIcon, MicIcon, MicOffIcon, MinimizeIcon, PinnedIcon, PipIcon, PlayIcon, RightArrowIcon, RotatePhoneIcon, SelectChevronIcon, SlidesIcon, SpeakNowMicIcon, SpinnerIcon, ThumbsDownIcon, ThumbsUpIcon, UpCaretIcon, WarningIcon, icons_CameraOffIcon as CameraOffIcon, icons_CameraOnIcon as CameraOnIcon, icons_CheckmarkIcon as CheckmarkIcon, icons_ChevronIcon as ChevronIcon, icons_CloseIcon as CloseIcon, icons_DoubleChatIcon as DoubleChatIcon, icons_PinIcon as PinIcon, icons_SoundwaveIcon as SoundwaveIcon };