@oxvo/ai-live-assist 7.3.4 → 7.4.23
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/cjs/AiLiveAssist.d.ts +588 -6
- package/cjs/AiLiveAssist.js +14892 -1240
- package/cjs/client.d.ts +71 -10
- package/cjs/client.js +316 -58
- package/cjs/context.d.ts +15 -5
- package/cjs/context.js +568 -73
- package/cjs/control.d.ts +72 -7
- package/cjs/control.js +594 -71
- package/cjs/dialogFocusScope.d.ts +37 -0
- package/cjs/dialogFocusScope.js +227 -0
- package/cjs/experienceState.js +1 -1
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +2 -2
- package/cjs/media.d.ts +14 -3
- package/cjs/media.js +128 -19
- package/cjs/messages.js +26 -3
- package/cjs/messengerLoader.d.ts +3 -0
- package/cjs/messengerLoader.js +156 -0
- package/cjs/pageAssistancePreference.d.ts +78 -0
- package/cjs/pageAssistancePreference.js +488 -0
- package/cjs/preview.d.ts +33 -0
- package/cjs/preview.js +567 -0
- package/cjs/tabLock.d.ts +54 -3
- package/cjs/tabLock.js +752 -72
- package/cjs/types.d.ts +152 -4
- package/cjs/ui.d.ts +200 -10
- package/cjs/ui.js +6946 -523
- package/cjs/version.d.ts +1 -1
- package/cjs/version.js +1 -1
- package/cjs/voicePresenceUi.d.ts +48 -9
- package/cjs/voicePresenceUi.js +863 -125
- package/lib/AiLiveAssist.d.ts +588 -6
- package/lib/AiLiveAssist.js +14897 -1248
- package/lib/client.d.ts +71 -10
- package/lib/client.js +313 -57
- package/lib/context.d.ts +15 -5
- package/lib/context.js +566 -72
- package/lib/control.d.ts +72 -7
- package/lib/control.js +594 -71
- package/lib/dialogFocusScope.d.ts +37 -0
- package/lib/dialogFocusScope.js +223 -0
- package/lib/experienceState.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -2
- package/lib/media.d.ts +14 -3
- package/lib/media.js +128 -19
- package/lib/messages.js +26 -3
- package/lib/messengerLoader.d.ts +3 -0
- package/lib/messengerLoader.js +151 -0
- package/lib/pageAssistancePreference.d.ts +78 -0
- package/lib/pageAssistancePreference.js +482 -0
- package/lib/preview.d.ts +33 -0
- package/lib/preview.js +561 -0
- package/lib/tabLock.d.ts +54 -3
- package/lib/tabLock.js +752 -72
- package/lib/types.d.ts +152 -4
- package/lib/ui.d.ts +200 -10
- package/lib/ui.js +6947 -524
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/voicePresenceUi.d.ts +48 -9
- package/lib/voicePresenceUi.js +863 -125
- package/package.json +8 -3
- package/cjs/recordingPolicy.d.ts +0 -2
- package/cjs/recordingPolicy.js +0 -12
- package/lib/recordingPolicy.d.ts +0 -2
- package/lib/recordingPolicy.js +0 -8
package/cjs/voicePresenceUi.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VoicePresenceView = void 0;
|
|
4
4
|
const lucide_1 = require("lucide");
|
|
5
|
+
const dialogFocusScope_js_1 = require("./dialogFocusScope.js");
|
|
5
6
|
const experienceState_js_1 = require("./experienceState.js");
|
|
6
7
|
const messages_js_1 = require("./messages.js");
|
|
7
8
|
const placement_js_1 = require("./placement.js");
|
|
@@ -52,6 +53,12 @@ const confirmationPresentation = (value, fallbackTitle, fallbackApprove) => {
|
|
|
52
53
|
const INTRO_WAKE_MS = 620;
|
|
53
54
|
const MAX_LIVE_ANNOUNCEMENTS = 6;
|
|
54
55
|
const MAX_VISIBLE_SUBTITLES = 3;
|
|
56
|
+
const PAGE_ASSISTANCE_TITLE = "Let AI Guide help on this page?";
|
|
57
|
+
const PAGE_ASSISTANCE_FALLBACK = "AI Guide wants to navigate or interact with this page.";
|
|
58
|
+
const PAGE_ASSISTANCE_ALLOW = "Allow";
|
|
59
|
+
const PAGE_ASSISTANCE_DECLINE = "Not now";
|
|
60
|
+
const PAGE_ASSISTANCE_ALWAYS = "Always allow on this site";
|
|
61
|
+
const PAGE_ASSISTANCE_REVOKE = "Turn off always allow";
|
|
55
62
|
const styles = `
|
|
56
63
|
:host {
|
|
57
64
|
all: initial;
|
|
@@ -136,6 +143,60 @@ const styles = `
|
|
|
136
143
|
inset-inline-start: max(var(--voice-inline-offset), env(safe-area-inset-left));
|
|
137
144
|
inset-inline-end: auto;
|
|
138
145
|
}
|
|
146
|
+
.page-assistance-revoke {
|
|
147
|
+
position: absolute;
|
|
148
|
+
inset-inline-end: calc(100% + 8px);
|
|
149
|
+
bottom: 9px;
|
|
150
|
+
width: max-content;
|
|
151
|
+
max-width: min(220px, calc(100vw - var(--voice-orb-size) - var(--voice-inline-offset) - 24px));
|
|
152
|
+
min-height: 44px;
|
|
153
|
+
padding: 7px 10px;
|
|
154
|
+
overflow-wrap: anywhere;
|
|
155
|
+
color: var(--voice-text);
|
|
156
|
+
background: var(--voice-surface);
|
|
157
|
+
border: 1px solid var(--voice-border);
|
|
158
|
+
border-radius: 999px;
|
|
159
|
+
box-shadow: 0 7px 20px rgba(15,23,42,.14);
|
|
160
|
+
font-size: 11px;
|
|
161
|
+
font-weight: 620;
|
|
162
|
+
line-height: 16px;
|
|
163
|
+
text-align: center;
|
|
164
|
+
white-space: normal;
|
|
165
|
+
pointer-events: auto;
|
|
166
|
+
}
|
|
167
|
+
.page-assistance-revoke:hover { background: #f7f8f9; }
|
|
168
|
+
:host([data-position="bottom_left"]) .page-assistance-revoke {
|
|
169
|
+
inset-inline-start: calc(100% + 8px);
|
|
170
|
+
inset-inline-end: auto;
|
|
171
|
+
}
|
|
172
|
+
.voice-support-action {
|
|
173
|
+
position: absolute;
|
|
174
|
+
inset-inline-end: 0;
|
|
175
|
+
bottom: calc(100% + 10px);
|
|
176
|
+
width: max-content;
|
|
177
|
+
max-width: min(260px, calc(100vw - 28px));
|
|
178
|
+
min-height: 38px;
|
|
179
|
+
padding: 7px 12px;
|
|
180
|
+
color: var(--voice-text);
|
|
181
|
+
background: rgba(255, 255, 255, .92);
|
|
182
|
+
border: 1px solid var(--voice-border);
|
|
183
|
+
border-radius: 999px;
|
|
184
|
+
box-shadow: 0 8px 24px rgba(15, 23, 42, .14);
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
font-weight: 650;
|
|
187
|
+
line-height: 17px;
|
|
188
|
+
text-align: center;
|
|
189
|
+
pointer-events: auto;
|
|
190
|
+
}
|
|
191
|
+
.voice-support-action:hover { background: #f7f8f9; }
|
|
192
|
+
.voice-support-action.has-reply {
|
|
193
|
+
border-color: color-mix(in srgb, var(--voice-accent) 34%, white);
|
|
194
|
+
box-shadow: 0 8px 24px color-mix(in srgb, var(--voice-accent) 16%, transparent);
|
|
195
|
+
}
|
|
196
|
+
:host([data-position="bottom_left"]) .voice-support-action {
|
|
197
|
+
inset-inline-start: 0;
|
|
198
|
+
inset-inline-end: auto;
|
|
199
|
+
}
|
|
139
200
|
/* Premium orb themes. Light is the backwards-compatible default. */
|
|
140
201
|
:host,
|
|
141
202
|
:host([data-orb-theme="light"]) {
|
|
@@ -668,11 +729,16 @@ const styles = `
|
|
|
668
729
|
.dialog-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: start; }
|
|
669
730
|
.dialog h2 { margin: 0; font-size: 18px; line-height: 25px; font-weight: 680; }
|
|
670
731
|
.dialog p { margin: 7px 0 0; color: var(--voice-muted); font-size: 14px; line-height: 21px; }
|
|
732
|
+
.page-assistance-dialog {
|
|
733
|
+
width: min(360px, calc(var(--voice-viewport-width, 100vw) - 32px));
|
|
734
|
+
padding: 16px;
|
|
735
|
+
}
|
|
736
|
+
.page-assistance-dialog h2 { font-size: 16px; line-height: 22px; }
|
|
671
737
|
.icon-button {
|
|
672
738
|
display: grid;
|
|
673
|
-
width:
|
|
674
|
-
height:
|
|
675
|
-
min-width:
|
|
739
|
+
width: 44px;
|
|
740
|
+
height: 44px;
|
|
741
|
+
min-width: 44px;
|
|
676
742
|
padding: 0;
|
|
677
743
|
place-items: center;
|
|
678
744
|
color: #4b5563;
|
|
@@ -682,7 +748,14 @@ const styles = `
|
|
|
682
748
|
}
|
|
683
749
|
.icon-button:hover { background: #f3f4f6; }
|
|
684
750
|
.icon-button svg { width: 18px; height: 18px; }
|
|
685
|
-
.
|
|
751
|
+
.page-assistance-copy { overflow-wrap: anywhere; }
|
|
752
|
+
.page-assistance-target {
|
|
753
|
+
color: var(--voice-text);
|
|
754
|
+
font-size: 12px;
|
|
755
|
+
line-height: 18px;
|
|
756
|
+
font-weight: 620;
|
|
757
|
+
}
|
|
758
|
+
.page-assistance-always {
|
|
686
759
|
display: grid;
|
|
687
760
|
grid-template-columns: 18px minmax(0, 1fr);
|
|
688
761
|
gap: 10px;
|
|
@@ -697,16 +770,24 @@ const styles = `
|
|
|
697
770
|
line-height: 19px;
|
|
698
771
|
cursor: pointer;
|
|
699
772
|
}
|
|
700
|
-
.
|
|
773
|
+
.page-assistance-always span { min-width: 0; overflow-wrap: anywhere; }
|
|
774
|
+
.page-assistance-always input { width: 17px; height: 17px; margin: 1px 0 0; accent-color: var(--voice-accent); }
|
|
701
775
|
details { margin-top: 12px; color: var(--voice-muted); font-size: 12px; line-height: 18px; }
|
|
702
|
-
summary {
|
|
776
|
+
summary {
|
|
777
|
+
display: inline-flex;
|
|
778
|
+
width: fit-content;
|
|
779
|
+
min-height: 44px;
|
|
780
|
+
align-items: center;
|
|
781
|
+
color: color-mix(in srgb, var(--voice-accent) 78%, #18212b);
|
|
782
|
+
cursor: pointer;
|
|
783
|
+
}
|
|
703
784
|
.legal-copy { margin-top: 8px; padding: 10px 11px; background: #fafbfc; border: 1px solid #e6e9ed; border-radius: 8px; }
|
|
704
785
|
.legal-copy p { margin: 0; font-size: 12px; line-height: 18px; }
|
|
705
786
|
.legal-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; }
|
|
706
787
|
.legal-links a { color: color-mix(in srgb, var(--voice-accent) 82%, #18212b); }
|
|
707
788
|
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
|
|
708
789
|
.button {
|
|
709
|
-
min-height:
|
|
790
|
+
min-height: 44px;
|
|
710
791
|
padding: 0 14px;
|
|
711
792
|
border: 1px solid #d7dce1;
|
|
712
793
|
border-radius: 8px;
|
|
@@ -738,10 +819,10 @@ const styles = `
|
|
|
738
819
|
.card-title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 680; }
|
|
739
820
|
.card-copy { margin: 5px 0 0; color: var(--voice-muted); font-size: 12px; line-height: 18px; }
|
|
740
821
|
.card-actions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 12px; }
|
|
741
|
-
.card-actions .button { min-height:
|
|
822
|
+
.card-actions .button { min-height: 44px; }
|
|
742
823
|
.recovery-head {
|
|
743
824
|
display: grid;
|
|
744
|
-
grid-template-columns: 28px minmax(0, 1fr)
|
|
825
|
+
grid-template-columns: 28px minmax(0, 1fr) 44px;
|
|
745
826
|
gap: 9px;
|
|
746
827
|
align-items: center;
|
|
747
828
|
}
|
|
@@ -757,9 +838,9 @@ const styles = `
|
|
|
757
838
|
}
|
|
758
839
|
.recovery-badge svg { width: 16px; height: 16px; }
|
|
759
840
|
.recovery-close {
|
|
760
|
-
width:
|
|
761
|
-
height:
|
|
762
|
-
min-width:
|
|
841
|
+
width: 44px;
|
|
842
|
+
height: 44px;
|
|
843
|
+
min-width: 44px;
|
|
763
844
|
justify-self: end;
|
|
764
845
|
}
|
|
765
846
|
.recovery-card .card-copy { margin-top: 9px; font-size: 13px; line-height: 19px; }
|
|
@@ -834,6 +915,37 @@ const styles = `
|
|
|
834
915
|
.orb.wake::after { animation-duration: 120ms; }
|
|
835
916
|
.presence-field { transition-duration: 120ms; }
|
|
836
917
|
}
|
|
918
|
+
:host([data-motion="off"]) .orb,
|
|
919
|
+
:host([data-motion="off"]) .orb::before,
|
|
920
|
+
:host([data-motion="off"]) .orb::after,
|
|
921
|
+
:host([data-motion="off"]) .orb-rim,
|
|
922
|
+
:host([data-motion="off"]) .orb-rim::before,
|
|
923
|
+
:host([data-motion="off"]) .orb-rim::after,
|
|
924
|
+
:host([data-motion="off"]) .orb-surface,
|
|
925
|
+
:host([data-motion="off"]) .orb-surface::before,
|
|
926
|
+
:host([data-motion="off"]) .orb-surface::after,
|
|
927
|
+
:host([data-motion="off"]) .orb-core,
|
|
928
|
+
:host([data-motion="off"]) .orb-core::before,
|
|
929
|
+
:host([data-motion="off"]) .orb-core::after {
|
|
930
|
+
animation: none !important;
|
|
931
|
+
transition: none !important;
|
|
932
|
+
will-change: auto !important;
|
|
933
|
+
}
|
|
934
|
+
:host([data-effects-paused="true"]) .orb,
|
|
935
|
+
:host([data-effects-paused="true"]) .orb::before,
|
|
936
|
+
:host([data-effects-paused="true"]) .orb::after,
|
|
937
|
+
:host([data-effects-paused="true"]) .orb-rim,
|
|
938
|
+
:host([data-effects-paused="true"]) .orb-rim::before,
|
|
939
|
+
:host([data-effects-paused="true"]) .orb-rim::after,
|
|
940
|
+
:host([data-effects-paused="true"]) .orb-surface,
|
|
941
|
+
:host([data-effects-paused="true"]) .orb-surface::before,
|
|
942
|
+
:host([data-effects-paused="true"]) .orb-surface::after,
|
|
943
|
+
:host([data-effects-paused="true"]) .orb-core,
|
|
944
|
+
:host([data-effects-paused="true"]) .orb-core::before,
|
|
945
|
+
:host([data-effects-paused="true"]) .orb-core::after {
|
|
946
|
+
animation-play-state: paused !important;
|
|
947
|
+
will-change: auto !important;
|
|
948
|
+
}
|
|
837
949
|
:host([data-visual-test="true"]) *,
|
|
838
950
|
:host([data-visual-test="true"]) *::before,
|
|
839
951
|
:host([data-visual-test="true"]) *::after {
|
|
@@ -841,10 +953,71 @@ const styles = `
|
|
|
841
953
|
transition: none !important;
|
|
842
954
|
}
|
|
843
955
|
:host([data-visual-test="true"]) .caption.entering { animation: none !important; }
|
|
956
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
957
|
+
.presence-field {
|
|
958
|
+
display: none;
|
|
959
|
+
opacity: 0;
|
|
960
|
+
transition: none;
|
|
961
|
+
}
|
|
962
|
+
.orb {
|
|
963
|
+
color: #fff;
|
|
964
|
+
background: #1f2937;
|
|
965
|
+
background-image: none;
|
|
966
|
+
border-color: #4b5563;
|
|
967
|
+
box-shadow: none;
|
|
968
|
+
-webkit-backdrop-filter: none;
|
|
969
|
+
backdrop-filter: none;
|
|
970
|
+
filter: none;
|
|
971
|
+
}
|
|
972
|
+
.orb::before,
|
|
973
|
+
.orb::after,
|
|
974
|
+
.orb-rim,
|
|
975
|
+
.orb-rim::before,
|
|
976
|
+
.orb-rim::after,
|
|
977
|
+
.orb-surface,
|
|
978
|
+
.orb-surface::before,
|
|
979
|
+
.orb-surface::after,
|
|
980
|
+
.orb-core::before,
|
|
981
|
+
.orb-core::after {
|
|
982
|
+
display: none;
|
|
983
|
+
animation: none;
|
|
984
|
+
background: none;
|
|
985
|
+
background-image: none;
|
|
986
|
+
box-shadow: none;
|
|
987
|
+
-webkit-backdrop-filter: none;
|
|
988
|
+
backdrop-filter: none;
|
|
989
|
+
filter: none;
|
|
990
|
+
}
|
|
991
|
+
.orb-core {
|
|
992
|
+
background: #111827;
|
|
993
|
+
background-image: none;
|
|
994
|
+
box-shadow: none;
|
|
995
|
+
-webkit-backdrop-filter: none;
|
|
996
|
+
backdrop-filter: none;
|
|
997
|
+
filter: none;
|
|
998
|
+
}
|
|
999
|
+
.orb-icon { filter: none; }
|
|
1000
|
+
.subtitle-track,
|
|
1001
|
+
.first-label,
|
|
1002
|
+
.status-bubble,
|
|
1003
|
+
.attribution-bubble,
|
|
1004
|
+
.dialog,
|
|
1005
|
+
.safety-card,
|
|
1006
|
+
.recovery-card,
|
|
1007
|
+
.page-assistance-revoke {
|
|
1008
|
+
background: #fff;
|
|
1009
|
+
background-image: none;
|
|
1010
|
+
box-shadow: none;
|
|
1011
|
+
-webkit-backdrop-filter: none;
|
|
1012
|
+
backdrop-filter: none;
|
|
1013
|
+
filter: none;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
844
1016
|
@media (forced-colors: active) {
|
|
845
1017
|
.orb { color: ButtonText; background: ButtonFace; border: 2px solid ButtonText; forced-color-adjust: auto; }
|
|
846
1018
|
.orb-surface, .orb-core, .orb-rim { display: none; }
|
|
847
1019
|
.subtitle-track, .dialog, .safety-card, .recovery-card { color: CanvasText; background: Canvas; border: 1px solid CanvasText; }
|
|
1020
|
+
.page-assistance-revoke { color: ButtonText; background: ButtonFace; border: 1px solid ButtonText; }
|
|
848
1021
|
.caption-speaker { color: CanvasText; background: Canvas; border-color: CanvasText; }
|
|
849
1022
|
}
|
|
850
1023
|
@media (max-width: 520px) {
|
|
@@ -853,8 +1026,12 @@ const styles = `
|
|
|
853
1026
|
width: 100%;
|
|
854
1027
|
max-height: calc(var(--voice-viewport-height, 100dvh) - 20px);
|
|
855
1028
|
}
|
|
1029
|
+
.page-assistance-dialog { border-radius: 14px 14px 8px 8px; }
|
|
856
1030
|
.dialog-actions { flex-direction: column-reverse; }
|
|
857
1031
|
.dialog-actions .button { width: 100%; }
|
|
1032
|
+
.page-assistance-revoke {
|
|
1033
|
+
max-width: calc(100vw - var(--voice-orb-size) - 36px);
|
|
1034
|
+
}
|
|
858
1035
|
}
|
|
859
1036
|
`;
|
|
860
1037
|
const icon = (node, className) => {
|
|
@@ -863,11 +1040,8 @@ const icon = (node, className) => {
|
|
|
863
1040
|
element.setAttribute("aria-hidden", "true");
|
|
864
1041
|
return element;
|
|
865
1042
|
};
|
|
866
|
-
const isFocusTarget = (value) => value instanceof HTMLElement &&
|
|
867
|
-
!value.hasAttribute("disabled") &&
|
|
868
|
-
value.getAttribute("aria-hidden") !== "true";
|
|
869
1043
|
class VoicePresenceView {
|
|
870
|
-
constructor(config, overrides, callbacks, tabId, onFallback = () => undefined, visualTestMode = false) {
|
|
1044
|
+
constructor(config, overrides, callbacks, tabId, onFallback = () => undefined, visualTestMode = false, options = {}) {
|
|
871
1045
|
this.callbacks = callbacks;
|
|
872
1046
|
this.onFallback = onFallback;
|
|
873
1047
|
this.captionTimers = new Map();
|
|
@@ -879,7 +1053,10 @@ class VoicePresenceView {
|
|
|
879
1053
|
this.highlightScrollTargets = [];
|
|
880
1054
|
this.currentConfirmation = null;
|
|
881
1055
|
this.currentVisualRequest = null;
|
|
882
|
-
this.
|
|
1056
|
+
this.currentHandoffContextRequest = null;
|
|
1057
|
+
this.handoffRequestPreparing = false;
|
|
1058
|
+
this.currentPageAssistanceRequest = null;
|
|
1059
|
+
this.pageAssistancePersistentAllowed = false;
|
|
883
1060
|
this.active = false;
|
|
884
1061
|
this.activeElsewhere = false;
|
|
885
1062
|
this.destroyed = false;
|
|
@@ -899,31 +1076,7 @@ class VoicePresenceView {
|
|
|
899
1076
|
this.serverPresenceState = "ready";
|
|
900
1077
|
this.playbackState = "idle";
|
|
901
1078
|
this.capabilityTier = "standard";
|
|
902
|
-
this.
|
|
903
|
-
if (event.key === "Escape") {
|
|
904
|
-
event.preventDefault();
|
|
905
|
-
this.closeActivation();
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
if (event.key !== "Tab")
|
|
909
|
-
return;
|
|
910
|
-
const focusable = Array.from(this.modalLayer.querySelectorAll("button,input,summary,a[href]")).filter(isFocusTarget);
|
|
911
|
-
if (focusable.length === 0)
|
|
912
|
-
return;
|
|
913
|
-
const first = focusable[0];
|
|
914
|
-
const last = focusable.at(-1);
|
|
915
|
-
if (!first || !last)
|
|
916
|
-
return;
|
|
917
|
-
const activeElement = this.shadow.activeElement;
|
|
918
|
-
if (event.shiftKey && activeElement === first) {
|
|
919
|
-
event.preventDefault();
|
|
920
|
-
last.focus();
|
|
921
|
-
}
|
|
922
|
-
else if (!event.shiftKey && activeElement === last) {
|
|
923
|
-
event.preventDefault();
|
|
924
|
-
first.focus();
|
|
925
|
-
}
|
|
926
|
-
};
|
|
1079
|
+
this.reducedMotionQuery = null;
|
|
927
1080
|
this.scheduleCaptionLayout = () => {
|
|
928
1081
|
if (this.destroyed || this.captionLayoutFrame !== null)
|
|
929
1082
|
return;
|
|
@@ -941,10 +1094,10 @@ class VoicePresenceView {
|
|
|
941
1094
|
});
|
|
942
1095
|
};
|
|
943
1096
|
this.onVisibilityChange = () => {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1097
|
+
this.syncMotionPolicy();
|
|
1098
|
+
};
|
|
1099
|
+
this.onReducedMotionChange = () => {
|
|
1100
|
+
this.syncMotionPolicy();
|
|
948
1101
|
};
|
|
949
1102
|
const voiceConfig = config.appearance?.voicePresence;
|
|
950
1103
|
if (!voiceConfig?.enabled) {
|
|
@@ -969,7 +1122,9 @@ class VoicePresenceView {
|
|
|
969
1122
|
document.documentElement.append(this.host);
|
|
970
1123
|
this.orb = this.require('[data-role="orb"]');
|
|
971
1124
|
this.modalLayer = this.require('[data-role="activation-layer"]');
|
|
972
|
-
this.
|
|
1125
|
+
this.pageAssistanceLayer = this.require('[data-role="page-assistance-layer"]');
|
|
1126
|
+
this.pageAssistanceAlways = this.require('[data-role="page-assistance-always"]');
|
|
1127
|
+
this.pageAssistanceRevoke = this.require('[data-role="page-assistance-revoke"]');
|
|
973
1128
|
this.captionStage = this.require(".caption-stage");
|
|
974
1129
|
this.subtitleTrack = this.require('[data-role="subtitle-track"]');
|
|
975
1130
|
this.statusBubble = this.require('[data-role="status-bubble"]');
|
|
@@ -979,6 +1134,10 @@ class VoicePresenceView {
|
|
|
979
1134
|
this.liveRegion = this.require('[data-role="live-region"]');
|
|
980
1135
|
this.audio = this.require("audio");
|
|
981
1136
|
this.highlight = this.require(".action-highlight");
|
|
1137
|
+
this.dialogFocus = new dialogFocusScope_js_1.DialogFocusScope(this.shadow, this.host);
|
|
1138
|
+
this.reducedMotionQuery =
|
|
1139
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)") ?? null;
|
|
1140
|
+
this.reducedMotionQuery?.addEventListener?.("change", this.onReducedMotionChange);
|
|
982
1141
|
if (typeof ResizeObserver !== "undefined") {
|
|
983
1142
|
this.captionResizeObserver = new ResizeObserver(this.scheduleCaptionLayout);
|
|
984
1143
|
this.highlightResizeObserver = new ResizeObserver(this.scheduleHighlightLayout);
|
|
@@ -988,6 +1147,13 @@ class VoicePresenceView {
|
|
|
988
1147
|
this.host.dataset.state = state;
|
|
989
1148
|
});
|
|
990
1149
|
this.applyConfig();
|
|
1150
|
+
if (options.previewMode) {
|
|
1151
|
+
for (const link of this.shadow.querySelectorAll("a")) {
|
|
1152
|
+
link.removeAttribute("href");
|
|
1153
|
+
link.removeAttribute("target");
|
|
1154
|
+
link.tabIndex = -1;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
991
1157
|
this.placement = new placement_js_1.LauncherPlacementController({
|
|
992
1158
|
host: this.host,
|
|
993
1159
|
preferredSide: this.voiceConfig.launcher.position,
|
|
@@ -998,8 +1164,97 @@ class VoicePresenceView {
|
|
|
998
1164
|
});
|
|
999
1165
|
this.bind();
|
|
1000
1166
|
this.bindPlaybackState();
|
|
1167
|
+
this.syncMotionPolicy();
|
|
1001
1168
|
this.stateMachine.transition("launcher");
|
|
1002
1169
|
}
|
|
1170
|
+
open(_mode = "voice") {
|
|
1171
|
+
void _mode;
|
|
1172
|
+
if (!this.active && !this.activeElsewhere)
|
|
1173
|
+
this.openActivation();
|
|
1174
|
+
}
|
|
1175
|
+
close() {
|
|
1176
|
+
if (this.currentPageAssistanceRequest) {
|
|
1177
|
+
this.resolvePageAssistanceRequest("decline");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (this.currentHandoffContextRequest) {
|
|
1181
|
+
this.resolveHandoffContextRequest(false);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (this.currentVisualRequest) {
|
|
1185
|
+
this.resolveVisualRequest(false);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (this.currentConfirmation) {
|
|
1189
|
+
this.resolveConfirmation(false);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
this.closeActivation();
|
|
1193
|
+
}
|
|
1194
|
+
get isOpen() {
|
|
1195
|
+
return !this.modalLayer.hidden || !this.pageAssistanceLayer.hidden;
|
|
1196
|
+
}
|
|
1197
|
+
rootElement() {
|
|
1198
|
+
return this.host;
|
|
1199
|
+
}
|
|
1200
|
+
setSupportCompanion(linked) {
|
|
1201
|
+
const action = this.require('[data-role="voice-support-action"]');
|
|
1202
|
+
action.hidden = !linked;
|
|
1203
|
+
if (linked && !action.classList.contains("has-reply")) {
|
|
1204
|
+
action.textContent = this.messages.support;
|
|
1205
|
+
action.setAttribute("aria-label", this.messages.support);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
showSupportReplyNotice(visible) {
|
|
1209
|
+
const action = this.require('[data-role="voice-support-action"]');
|
|
1210
|
+
action.classList.toggle("has-reply", visible);
|
|
1211
|
+
action.textContent = visible
|
|
1212
|
+
? `${this.messages.supportReply} · ${this.messages.viewSupport}`
|
|
1213
|
+
: this.messages.support;
|
|
1214
|
+
action.setAttribute("aria-label", action.textContent);
|
|
1215
|
+
}
|
|
1216
|
+
updatePreviewConfig(config) {
|
|
1217
|
+
const voiceConfig = config.appearance?.voicePresence;
|
|
1218
|
+
if (!voiceConfig?.enabled)
|
|
1219
|
+
return false;
|
|
1220
|
+
this.config = config;
|
|
1221
|
+
this.voiceConfig = voiceConfig;
|
|
1222
|
+
this.host.dataset.position = voiceConfig.launcher.position;
|
|
1223
|
+
this.host.dataset.intensity = voiceConfig.visual.intensity;
|
|
1224
|
+
this.setOrbTheme(voiceConfig.launcher.theme ?? "light");
|
|
1225
|
+
this.applyConfig();
|
|
1226
|
+
this.orb.setAttribute("aria-label", this.launcherLabel());
|
|
1227
|
+
const firstLabel = this.shadow.querySelector('[data-role="first-label"]');
|
|
1228
|
+
if (firstLabel) {
|
|
1229
|
+
firstLabel.textContent = config.appearance?.launcherLabel || "Talk to us";
|
|
1230
|
+
}
|
|
1231
|
+
const title = this.shadow.querySelector("#oxvo-voice-title");
|
|
1232
|
+
const description = this.shadow.querySelector("#oxvo-voice-description");
|
|
1233
|
+
const close = this.shadow.querySelector('[data-role="activation-close"]');
|
|
1234
|
+
const secondary = this.shadow.querySelector('[data-role="activation-secondary"]');
|
|
1235
|
+
const primary = this.shadow.querySelector('[data-role="activation-primary"]');
|
|
1236
|
+
if (title)
|
|
1237
|
+
title.textContent = voiceConfig.activation.title;
|
|
1238
|
+
if (description)
|
|
1239
|
+
description.textContent = voiceConfig.activation.description;
|
|
1240
|
+
close?.setAttribute("aria-label", voiceConfig.activation.secondaryLabel);
|
|
1241
|
+
if (secondary)
|
|
1242
|
+
secondary.textContent = voiceConfig.activation.secondaryLabel;
|
|
1243
|
+
if (primary)
|
|
1244
|
+
primary.textContent = voiceConfig.activation.primaryLabel;
|
|
1245
|
+
const details = this.modalLayer.querySelector("details");
|
|
1246
|
+
const summary = details?.querySelector("summary");
|
|
1247
|
+
if (summary) {
|
|
1248
|
+
summary.textContent =
|
|
1249
|
+
voiceConfig.activation.detailsLabel || this.messages.legalDetails;
|
|
1250
|
+
}
|
|
1251
|
+
const disclosure = details?.querySelector(".legal-copy p");
|
|
1252
|
+
if (disclosure) {
|
|
1253
|
+
disclosure.textContent =
|
|
1254
|
+
config.consent?.disclosure || this.messages.disclosure;
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1003
1258
|
setRemoteStream(stream) {
|
|
1004
1259
|
const previous = this.audio.srcObject;
|
|
1005
1260
|
if (previous &&
|
|
@@ -1018,6 +1273,7 @@ class VoicePresenceView {
|
|
|
1018
1273
|
}
|
|
1019
1274
|
setCapabilityTier(tier) {
|
|
1020
1275
|
this.capabilityTier = tier;
|
|
1276
|
+
this.syncMotionPolicy();
|
|
1021
1277
|
}
|
|
1022
1278
|
setOrbTheme(theme) {
|
|
1023
1279
|
this.host.dataset.orbTheme = theme;
|
|
@@ -1077,14 +1333,12 @@ class VoicePresenceView {
|
|
|
1077
1333
|
setAssistantState(state) {
|
|
1078
1334
|
if (!this.active)
|
|
1079
1335
|
return;
|
|
1336
|
+
this.serverPresenceState = state;
|
|
1080
1337
|
if (state === "thinking" || state === "speaking") {
|
|
1081
1338
|
this.resumeRemotePlayback();
|
|
1082
1339
|
}
|
|
1083
|
-
this.stateMachine.transition(
|
|
1084
|
-
|
|
1085
|
-
this.startLevelLoop();
|
|
1086
|
-
else
|
|
1087
|
-
this.stopLevelLoop();
|
|
1340
|
+
this.stateMachine.transition(this.effectivePresenceState());
|
|
1341
|
+
this.syncLevelLoop();
|
|
1088
1342
|
}
|
|
1089
1343
|
applyPresenceState(state, sequence, sessionId, pageId, fencingToken) {
|
|
1090
1344
|
this.serverPresenceState = state;
|
|
@@ -1109,7 +1363,7 @@ class VoicePresenceView {
|
|
|
1109
1363
|
this.closeActivation(false);
|
|
1110
1364
|
}
|
|
1111
1365
|
showRequestingMicrophone() {
|
|
1112
|
-
this.
|
|
1366
|
+
this.hideRecoveryCard(false);
|
|
1113
1367
|
this.orb.hidden = false;
|
|
1114
1368
|
this.stateMachine.transition("requesting_microphone");
|
|
1115
1369
|
this.showTransientStatus("Requesting microphone access", 0);
|
|
@@ -1139,7 +1393,7 @@ class VoicePresenceView {
|
|
|
1139
1393
|
this.stateMachine.transition("active.ready");
|
|
1140
1394
|
if (!this.introPlayed &&
|
|
1141
1395
|
this.voiceConfig.visual.introWake &&
|
|
1142
|
-
this.
|
|
1396
|
+
this.motionEnabled()) {
|
|
1143
1397
|
this.introPlayed = true;
|
|
1144
1398
|
this.orb.classList.add("wake");
|
|
1145
1399
|
this.wakeTimer = setTimeout(() => {
|
|
@@ -1204,12 +1458,29 @@ class VoicePresenceView {
|
|
|
1204
1458
|
this.announce(`${role === "assistant" ? "AI" : "You"}: ${normalized}`);
|
|
1205
1459
|
}
|
|
1206
1460
|
showConfirmation(value) {
|
|
1461
|
+
this.dismissHandoffRequestPreparing();
|
|
1462
|
+
if (this.currentConfirmation?.actionId === value.actionId &&
|
|
1463
|
+
this.currentConfirmation.proposalChecksum === value.proposalChecksum) {
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (this.currentHandoffContextRequest) {
|
|
1467
|
+
this.resolveHandoffContextRequest(false);
|
|
1468
|
+
}
|
|
1469
|
+
if (this.currentVisualRequest)
|
|
1470
|
+
this.resolveVisualRequest(false);
|
|
1471
|
+
if (this.currentConfirmation)
|
|
1472
|
+
this.resolveConfirmation(false);
|
|
1207
1473
|
this.currentConfirmation = value;
|
|
1208
|
-
this.currentVisualRequest = null;
|
|
1209
1474
|
this.hideStatus();
|
|
1210
1475
|
this.stateMachine.transition("active.awaiting_confirmation");
|
|
1211
1476
|
const presentation = confirmationPresentation(value, this.messages.actionRequest, this.messages.approve);
|
|
1212
|
-
this.renderSafetyCard(presentation.title, presentation.copy, presentation.approve, this.messages.decline, () => this.
|
|
1477
|
+
this.renderSafetyCard(presentation.title, presentation.copy, presentation.approve, this.messages.decline, () => this.resolvePendingConfirmation(true, {
|
|
1478
|
+
actionId: value.actionId,
|
|
1479
|
+
proposalChecksum: value.proposalChecksum,
|
|
1480
|
+
}), () => this.resolvePendingConfirmation(false, {
|
|
1481
|
+
actionId: value.actionId,
|
|
1482
|
+
proposalChecksum: value.proposalChecksum,
|
|
1483
|
+
}));
|
|
1213
1484
|
}
|
|
1214
1485
|
showActionState(status) {
|
|
1215
1486
|
this.currentConfirmation = null;
|
|
@@ -1224,7 +1495,9 @@ class VoicePresenceView {
|
|
|
1224
1495
|
this.hideStatus();
|
|
1225
1496
|
return;
|
|
1226
1497
|
}
|
|
1227
|
-
this.showTransientStatus(
|
|
1498
|
+
this.showTransientStatus(status === "unconfirmed"
|
|
1499
|
+
? this.messages.actionVerificationExpired
|
|
1500
|
+
: this.messages.actionFailed, status === "unconfirmed" ? 6000 : 3500);
|
|
1228
1501
|
}
|
|
1229
1502
|
dismissActionState() {
|
|
1230
1503
|
this.currentConfirmation = null;
|
|
@@ -1234,18 +1507,182 @@ class VoicePresenceView {
|
|
|
1234
1507
|
this.stateMachine.transition("active.ready");
|
|
1235
1508
|
}
|
|
1236
1509
|
showVisualRequest(value) {
|
|
1510
|
+
this.dismissHandoffRequestPreparing();
|
|
1511
|
+
if (this.currentPageAssistanceRequest) {
|
|
1512
|
+
this.resolvePageAssistanceRequest("decline");
|
|
1513
|
+
}
|
|
1514
|
+
if (this.currentHandoffContextRequest) {
|
|
1515
|
+
this.resolveHandoffContextRequest(false);
|
|
1516
|
+
}
|
|
1517
|
+
if (this.currentVisualRequest)
|
|
1518
|
+
this.resolveVisualRequest(false);
|
|
1519
|
+
if (this.currentConfirmation)
|
|
1520
|
+
this.resolveConfirmation(false);
|
|
1237
1521
|
this.currentVisualRequest = value;
|
|
1238
|
-
this.currentConfirmation = null;
|
|
1239
1522
|
this.hideStatus();
|
|
1240
1523
|
this.stateMachine.transition("active.awaiting_confirmation");
|
|
1241
|
-
this.renderSafetyCard(this.messages.visualRequest, value.reason, this.messages.shareVisual, this.messages.declineVisual, () =>
|
|
1524
|
+
this.renderSafetyCard(this.messages.visualRequest, value.reason, this.messages.shareVisual, this.messages.declineVisual, () => {
|
|
1525
|
+
if (this.currentVisualRequest === value)
|
|
1526
|
+
this.resolveVisualRequest(true);
|
|
1527
|
+
}, () => {
|
|
1528
|
+
if (this.currentVisualRequest === value)
|
|
1529
|
+
this.resolveVisualRequest(false);
|
|
1530
|
+
});
|
|
1242
1531
|
}
|
|
1243
1532
|
showVisualResult(shared) {
|
|
1533
|
+
if (this.currentHandoffContextRequest)
|
|
1534
|
+
return;
|
|
1244
1535
|
this.currentVisualRequest = null;
|
|
1245
1536
|
this.hideSafetyCard();
|
|
1246
1537
|
this.stateMachine.transition("active.ready");
|
|
1247
1538
|
this.showTransientStatus(shared ? this.messages.visualShared : this.messages.visualUnavailable, 3500);
|
|
1248
1539
|
}
|
|
1540
|
+
showHandoffRequestPreparing(title, description) {
|
|
1541
|
+
if (this.currentHandoffContextRequest || this.handoffRequestPreparing) {
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
this.handoffRequestPreparing = true;
|
|
1545
|
+
this.hideStatus();
|
|
1546
|
+
this.stateMachine.transition("active.awaiting_confirmation");
|
|
1547
|
+
const heading = document.createElement("h3");
|
|
1548
|
+
heading.className = "card-title";
|
|
1549
|
+
heading.textContent = title.slice(0, 160);
|
|
1550
|
+
const body = document.createElement("p");
|
|
1551
|
+
body.className = "card-copy";
|
|
1552
|
+
body.textContent = description.slice(0, 600);
|
|
1553
|
+
this.safetyCard.replaceChildren(heading, body);
|
|
1554
|
+
this.safetyCard.setAttribute("role", "status");
|
|
1555
|
+
this.safetyCard.setAttribute("aria-live", "polite");
|
|
1556
|
+
this.safetyCard.setAttribute("aria-busy", "true");
|
|
1557
|
+
this.safetyCard.removeAttribute("aria-modal");
|
|
1558
|
+
this.safetyCard.removeAttribute("aria-labelledby");
|
|
1559
|
+
this.safetyCard.removeAttribute("aria-describedby");
|
|
1560
|
+
this.safetyCard.hidden = false;
|
|
1561
|
+
this.syncSubtitleOcclusion();
|
|
1562
|
+
}
|
|
1563
|
+
dismissHandoffRequestPreparing() {
|
|
1564
|
+
if (!this.handoffRequestPreparing)
|
|
1565
|
+
return;
|
|
1566
|
+
this.handoffRequestPreparing = false;
|
|
1567
|
+
this.hideSafetyCard();
|
|
1568
|
+
if (this.active)
|
|
1569
|
+
this.stateMachine.transition("active.ready");
|
|
1570
|
+
}
|
|
1571
|
+
showHandoffContextRequest(value) {
|
|
1572
|
+
this.dismissHandoffRequestPreparing();
|
|
1573
|
+
const exactRequest = this.currentHandoffContextRequest?.requestId === value.requestId &&
|
|
1574
|
+
this.currentHandoffContextRequest.requestChecksum ===
|
|
1575
|
+
value.requestChecksum;
|
|
1576
|
+
if (exactRequest &&
|
|
1577
|
+
!this.safetyCard.hidden &&
|
|
1578
|
+
this.safetyCard.isConnected &&
|
|
1579
|
+
this.safetyCard.querySelector('[data-role="safety-decline"]') &&
|
|
1580
|
+
this.safetyCard.querySelector('[data-role="safety-approve"]')) {
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
if (!exactRequest) {
|
|
1584
|
+
if (this.currentPageAssistanceRequest) {
|
|
1585
|
+
this.resolvePageAssistanceRequest("decline");
|
|
1586
|
+
}
|
|
1587
|
+
if (this.currentVisualRequest)
|
|
1588
|
+
this.resolveVisualRequest(false);
|
|
1589
|
+
if (this.currentHandoffContextRequest) {
|
|
1590
|
+
this.resolveHandoffContextRequest(false);
|
|
1591
|
+
}
|
|
1592
|
+
if (this.currentConfirmation)
|
|
1593
|
+
this.resolveConfirmation(false);
|
|
1594
|
+
this.currentHandoffContextRequest = value;
|
|
1595
|
+
}
|
|
1596
|
+
const activeRequest = exactRequest
|
|
1597
|
+
? this.currentHandoffContextRequest
|
|
1598
|
+
: value;
|
|
1599
|
+
this.hideStatus();
|
|
1600
|
+
this.stateMachine.transition("active.awaiting_confirmation");
|
|
1601
|
+
this.renderSafetyCard(activeRequest.title, activeRequest.description, this.messages.handoffContextShare, this.messages.handoffContextNotNow, () => {
|
|
1602
|
+
if (this.currentHandoffContextRequest === activeRequest)
|
|
1603
|
+
this.resolveHandoffContextRequest(true);
|
|
1604
|
+
}, () => {
|
|
1605
|
+
if (this.currentHandoffContextRequest === activeRequest)
|
|
1606
|
+
this.resolveHandoffContextRequest(false);
|
|
1607
|
+
});
|
|
1608
|
+
return Boolean(this.currentHandoffContextRequest?.requestId === activeRequest.requestId &&
|
|
1609
|
+
this.currentHandoffContextRequest.requestChecksum ===
|
|
1610
|
+
activeRequest.requestChecksum &&
|
|
1611
|
+
!this.safetyCard.hidden &&
|
|
1612
|
+
this.safetyCard.isConnected &&
|
|
1613
|
+
this.safetyCard.querySelector('[data-role="safety-decline"]') &&
|
|
1614
|
+
this.safetyCard.querySelector('[data-role="safety-approve"]'));
|
|
1615
|
+
}
|
|
1616
|
+
dismissHandoffContextRequest() {
|
|
1617
|
+
this.dismissHandoffRequestPreparing();
|
|
1618
|
+
this.currentHandoffContextRequest = null;
|
|
1619
|
+
if (this.currentConfirmation ||
|
|
1620
|
+
this.currentVisualRequest ||
|
|
1621
|
+
this.currentPageAssistanceRequest) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
this.hideSafetyCard();
|
|
1625
|
+
if (this.active)
|
|
1626
|
+
this.stateMachine.transition("active.ready");
|
|
1627
|
+
}
|
|
1628
|
+
showHandoffContextResult(status) {
|
|
1629
|
+
this.dismissHandoffContextRequest();
|
|
1630
|
+
if (status === "granted") {
|
|
1631
|
+
this.showTransientStatus(this.messages.humanConnecting, 3500);
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (status === "expired" || status === "unavailable") {
|
|
1635
|
+
this.showTransientStatus(this.messages.humanUnavailable, 3500);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
this.hideStatus();
|
|
1639
|
+
}
|
|
1640
|
+
showPageAssistanceRequest(value) {
|
|
1641
|
+
this.dismissHandoffRequestPreparing();
|
|
1642
|
+
if (this.currentConfirmation)
|
|
1643
|
+
this.resolveConfirmation(false);
|
|
1644
|
+
if (this.currentVisualRequest)
|
|
1645
|
+
this.resolveVisualRequest(false);
|
|
1646
|
+
if (this.currentHandoffContextRequest) {
|
|
1647
|
+
this.resolveHandoffContextRequest(false);
|
|
1648
|
+
}
|
|
1649
|
+
if (this.currentPageAssistanceRequest?.requestId === value.requestId)
|
|
1650
|
+
return;
|
|
1651
|
+
if (this.currentPageAssistanceRequest) {
|
|
1652
|
+
this.resolvePageAssistanceRequest("decline", false);
|
|
1653
|
+
}
|
|
1654
|
+
this.currentPageAssistanceRequest = value;
|
|
1655
|
+
const description = compactUiText(value.safeActionDescription, 360);
|
|
1656
|
+
this.require('[data-role="page-assistance-description"]').textContent =
|
|
1657
|
+
description || PAGE_ASSISTANCE_FALLBACK;
|
|
1658
|
+
this.require('[data-role="page-assistance-target"]').textContent =
|
|
1659
|
+
compactUiText(value.safeTargetLabel, 160);
|
|
1660
|
+
const alwaysLabel = this.require('[data-role="page-assistance-always-label"]');
|
|
1661
|
+
this.pageAssistanceAlways.checked = false;
|
|
1662
|
+
alwaysLabel.hidden = !value.persistenceEligible;
|
|
1663
|
+
this.pageAssistanceLayer.hidden = false;
|
|
1664
|
+
this.syncPageAssistanceRevoke();
|
|
1665
|
+
this.hideStatus();
|
|
1666
|
+
this.syncSubtitleOcclusion();
|
|
1667
|
+
if (this.active)
|
|
1668
|
+
this.stateMachine.transition("active.awaiting_confirmation");
|
|
1669
|
+
this.callbacks.onOpenChange?.(true);
|
|
1670
|
+
this.dialogFocus.present({
|
|
1671
|
+
surface: this.pageAssistanceLayer,
|
|
1672
|
+
initialFocus: () => this.require('[data-role="page-assistance-decline"]'),
|
|
1673
|
+
fallbackFocus: () => (this.orb.hidden ? null : this.orb),
|
|
1674
|
+
onEscape: () => {
|
|
1675
|
+
this.resolvePageAssistanceRequest("decline");
|
|
1676
|
+
},
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
dismissPageAssistanceRequest() {
|
|
1680
|
+
this.hidePageAssistanceRequest(true);
|
|
1681
|
+
}
|
|
1682
|
+
setPageAssistancePersistentAllowed(allowed) {
|
|
1683
|
+
this.pageAssistancePersistentAllowed = allowed;
|
|
1684
|
+
this.syncPageAssistanceRevoke();
|
|
1685
|
+
}
|
|
1249
1686
|
setVisualAvailable(_available, _paused = false) {
|
|
1250
1687
|
void _available;
|
|
1251
1688
|
void _paused;
|
|
@@ -1281,6 +1718,11 @@ class VoicePresenceView {
|
|
|
1281
1718
|
this.stateMachine.transition("active.ready");
|
|
1282
1719
|
}
|
|
1283
1720
|
showError(message, retryable = false, sessionActive = this.active) {
|
|
1721
|
+
this.hidePageAssistanceRequest(false);
|
|
1722
|
+
this.currentConfirmation = null;
|
|
1723
|
+
this.currentVisualRequest = null;
|
|
1724
|
+
this.currentHandoffContextRequest = null;
|
|
1725
|
+
this.hideSafetyCard();
|
|
1284
1726
|
const safeMessage = message || this.messages.error;
|
|
1285
1727
|
if (sessionActive && this.active) {
|
|
1286
1728
|
this.stateMachine.transition(retryable ? "recoverable_error" : "terminal_error");
|
|
@@ -1293,11 +1735,66 @@ class VoicePresenceView {
|
|
|
1293
1735
|
this.orb.hidden = true;
|
|
1294
1736
|
this.renderRecoveryCard(safeMessage, retryable);
|
|
1295
1737
|
}
|
|
1738
|
+
showEndingDetached(message) {
|
|
1739
|
+
this.active = false;
|
|
1740
|
+
this.hidePageAssistanceRequest(false);
|
|
1741
|
+
this.currentConfirmation = null;
|
|
1742
|
+
this.currentVisualRequest = null;
|
|
1743
|
+
this.currentHandoffContextRequest = null;
|
|
1744
|
+
this.hideSafetyCard();
|
|
1745
|
+
this.hideRecoveryCard(false);
|
|
1746
|
+
this.clearCaptions();
|
|
1747
|
+
this.hideStatus();
|
|
1748
|
+
this.hideAttribution();
|
|
1749
|
+
this.stopAudio();
|
|
1750
|
+
this.stateMachine.transition("ending");
|
|
1751
|
+
this.orb.dataset.active = "false";
|
|
1752
|
+
this.orb.hidden = true;
|
|
1753
|
+
this.showTransientStatus(message || this.messages.ending, 0);
|
|
1754
|
+
this.placement.refresh();
|
|
1755
|
+
}
|
|
1756
|
+
showEndRetry(message) {
|
|
1757
|
+
this.active = true;
|
|
1758
|
+
this.orb.hidden = false;
|
|
1759
|
+
this.orb.dataset.active = "true";
|
|
1760
|
+
this.stateMachine.transition("terminal_error");
|
|
1761
|
+
this.renderRecoveryCard(message || this.messages.endRetry, false, {
|
|
1762
|
+
label: this.messages.end,
|
|
1763
|
+
onSelect: () => {
|
|
1764
|
+
this.hideRecoveryCard();
|
|
1765
|
+
this.stateMachine.transition("ending");
|
|
1766
|
+
this.callbacks.onEnd();
|
|
1767
|
+
},
|
|
1768
|
+
});
|
|
1769
|
+
this.placement.refresh();
|
|
1770
|
+
}
|
|
1771
|
+
showResumeStopped() {
|
|
1772
|
+
this.active = false;
|
|
1773
|
+
this.hidePageAssistanceRequest(false);
|
|
1774
|
+
this.currentConfirmation = null;
|
|
1775
|
+
this.currentVisualRequest = null;
|
|
1776
|
+
this.currentHandoffContextRequest = null;
|
|
1777
|
+
this.hideSafetyCard();
|
|
1778
|
+
this.hideRecoveryCard(false);
|
|
1779
|
+
this.clearCaptions();
|
|
1780
|
+
this.hideStatus();
|
|
1781
|
+
this.hideAttribution();
|
|
1782
|
+
this.stopAudio();
|
|
1783
|
+
this.orb.dataset.active = "false";
|
|
1784
|
+
this.orb.hidden = false;
|
|
1785
|
+
this.orb.setAttribute("aria-label", this.launcherLabel());
|
|
1786
|
+
this.stateMachine.reset();
|
|
1787
|
+
this.stateMachine.transition("launcher");
|
|
1788
|
+
this.placement.refresh();
|
|
1789
|
+
}
|
|
1296
1790
|
showEnded(message) {
|
|
1297
1791
|
this.active = false;
|
|
1792
|
+
this.hidePageAssistanceRequest(false);
|
|
1793
|
+
this.currentConfirmation = null;
|
|
1794
|
+
this.currentVisualRequest = null;
|
|
1795
|
+
this.currentHandoffContextRequest = null;
|
|
1298
1796
|
this.hideSafetyCard();
|
|
1299
|
-
this.
|
|
1300
|
-
this.recoveryCard.replaceChildren();
|
|
1797
|
+
this.hideRecoveryCard(false);
|
|
1301
1798
|
this.clearCaptions();
|
|
1302
1799
|
this.hideStatus();
|
|
1303
1800
|
this.stopAudio();
|
|
@@ -1320,9 +1817,13 @@ class VoicePresenceView {
|
|
|
1320
1817
|
this.orb.hidden = active;
|
|
1321
1818
|
this.orb.setAttribute("aria-label", active ? this.messages.activeElsewhere : this.launcherLabel());
|
|
1322
1819
|
if (active) {
|
|
1820
|
+
this.hidePageAssistanceRequest(false);
|
|
1821
|
+
this.currentConfirmation = null;
|
|
1822
|
+
this.currentVisualRequest = null;
|
|
1823
|
+
this.currentHandoffContextRequest = null;
|
|
1323
1824
|
this.closeActivation(false);
|
|
1324
1825
|
this.hideSafetyCard();
|
|
1325
|
-
this.
|
|
1826
|
+
this.hideRecoveryCard(false);
|
|
1326
1827
|
this.hideStatus();
|
|
1327
1828
|
return;
|
|
1328
1829
|
}
|
|
@@ -1330,6 +1831,11 @@ class VoicePresenceView {
|
|
|
1330
1831
|
}
|
|
1331
1832
|
showLaunchUnavailable(message) {
|
|
1332
1833
|
this.active = false;
|
|
1834
|
+
this.hidePageAssistanceRequest(false);
|
|
1835
|
+
this.currentConfirmation = null;
|
|
1836
|
+
this.currentVisualRequest = null;
|
|
1837
|
+
this.currentHandoffContextRequest = null;
|
|
1838
|
+
this.hideSafetyCard();
|
|
1333
1839
|
this.orb.hidden = true;
|
|
1334
1840
|
this.renderRecoveryCard(message, false);
|
|
1335
1841
|
this.stateMachine.reset();
|
|
@@ -1379,15 +1885,25 @@ class VoicePresenceView {
|
|
|
1379
1885
|
cancelPendingConfirmation() {
|
|
1380
1886
|
if (this.resolvePendingConfirmation(false))
|
|
1381
1887
|
return true;
|
|
1888
|
+
if (this.currentHandoffContextRequest) {
|
|
1889
|
+
this.resolveHandoffContextRequest(false);
|
|
1890
|
+
return true;
|
|
1891
|
+
}
|
|
1382
1892
|
if (this.currentVisualRequest) {
|
|
1383
1893
|
this.resolveVisualRequest(false);
|
|
1384
1894
|
return true;
|
|
1385
1895
|
}
|
|
1386
1896
|
return false;
|
|
1387
1897
|
}
|
|
1388
|
-
resolvePendingConfirmation(approved) {
|
|
1389
|
-
|
|
1898
|
+
resolvePendingConfirmation(approved, expected) {
|
|
1899
|
+
const current = this.currentConfirmation;
|
|
1900
|
+
if (!current)
|
|
1901
|
+
return false;
|
|
1902
|
+
if (expected &&
|
|
1903
|
+
(current.actionId !== expected.actionId ||
|
|
1904
|
+
current.proposalChecksum !== expected.proposalChecksum)) {
|
|
1390
1905
|
return false;
|
|
1906
|
+
}
|
|
1391
1907
|
this.resolveConfirmation(approved);
|
|
1392
1908
|
return true;
|
|
1393
1909
|
}
|
|
@@ -1396,11 +1912,11 @@ class VoicePresenceView {
|
|
|
1396
1912
|
return;
|
|
1397
1913
|
this.destroyed = true;
|
|
1398
1914
|
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
1915
|
+
this.reducedMotionQuery?.removeEventListener?.("change", this.onReducedMotionChange);
|
|
1399
1916
|
document.removeEventListener("scroll", this.scheduleHighlightLayout, true);
|
|
1400
1917
|
window.removeEventListener("resize", this.scheduleHighlightLayout);
|
|
1401
1918
|
window.visualViewport?.removeEventListener("resize", this.scheduleHighlightLayout);
|
|
1402
1919
|
window.visualViewport?.removeEventListener("scroll", this.scheduleHighlightLayout);
|
|
1403
|
-
this.modalLayer.removeEventListener("keydown", this.onModalKeyDown);
|
|
1404
1920
|
if (this.wakeTimer)
|
|
1405
1921
|
clearTimeout(this.wakeTimer);
|
|
1406
1922
|
if (this.statusTimer)
|
|
@@ -1414,6 +1930,7 @@ class VoicePresenceView {
|
|
|
1414
1930
|
this.captionResizeObserver?.disconnect();
|
|
1415
1931
|
this.highlightResizeObserver?.disconnect();
|
|
1416
1932
|
this.unbindHighlightScrollTargets();
|
|
1933
|
+
this.dialogFocus.destroy();
|
|
1417
1934
|
this.placement.destroy();
|
|
1418
1935
|
this.clearCaptions();
|
|
1419
1936
|
this.stopAudio();
|
|
@@ -1456,13 +1973,30 @@ class VoicePresenceView {
|
|
|
1456
1973
|
safety.dataset.role = "safety-card";
|
|
1457
1974
|
safety.hidden = true;
|
|
1458
1975
|
safety.setAttribute("role", "alertdialog");
|
|
1459
|
-
safety.setAttribute("aria-modal", "
|
|
1976
|
+
safety.setAttribute("aria-modal", "true");
|
|
1977
|
+
safety.setAttribute("aria-labelledby", "oxvo-voice-safety-title");
|
|
1978
|
+
safety.setAttribute("aria-describedby", "oxvo-voice-safety-copy");
|
|
1460
1979
|
const recovery = document.createElement("div");
|
|
1461
1980
|
recovery.className = "recovery-card";
|
|
1462
1981
|
recovery.dataset.role = "recovery-card";
|
|
1463
1982
|
recovery.hidden = true;
|
|
1464
1983
|
recovery.setAttribute("role", "alertdialog");
|
|
1465
|
-
recovery.setAttribute("aria-modal", "
|
|
1984
|
+
recovery.setAttribute("aria-modal", "true");
|
|
1985
|
+
const pageAssistanceRevoke = document.createElement("button");
|
|
1986
|
+
pageAssistanceRevoke.type = "button";
|
|
1987
|
+
pageAssistanceRevoke.className = "page-assistance-revoke";
|
|
1988
|
+
pageAssistanceRevoke.dataset.role = "page-assistance-revoke";
|
|
1989
|
+
pageAssistanceRevoke.textContent = PAGE_ASSISTANCE_REVOKE;
|
|
1990
|
+
pageAssistanceRevoke.setAttribute("aria-label", PAGE_ASSISTANCE_REVOKE);
|
|
1991
|
+
pageAssistanceRevoke.hidden = true;
|
|
1992
|
+
const supportAction = document.createElement("button");
|
|
1993
|
+
supportAction.type = "button";
|
|
1994
|
+
supportAction.className = "voice-support-action";
|
|
1995
|
+
supportAction.dataset.role = "voice-support-action";
|
|
1996
|
+
supportAction.textContent = this.messages.support;
|
|
1997
|
+
supportAction.setAttribute("aria-label", this.messages.support);
|
|
1998
|
+
supportAction.setAttribute("aria-live", "polite");
|
|
1999
|
+
supportAction.hidden = true;
|
|
1466
2000
|
const orb = document.createElement("button");
|
|
1467
2001
|
orb.type = "button";
|
|
1468
2002
|
orb.className = "orb";
|
|
@@ -1496,7 +2030,7 @@ class VoicePresenceView {
|
|
|
1496
2030
|
label.textContent = this.config.appearance?.launcherLabel || "Talk to us";
|
|
1497
2031
|
orbWrap.append(label);
|
|
1498
2032
|
}
|
|
1499
|
-
orbWrap.append(captions, status, attribution, safety, recovery, orb);
|
|
2033
|
+
orbWrap.append(captions, status, attribution, safety, recovery, pageAssistanceRevoke, supportAction, orb);
|
|
1500
2034
|
const modalLayer = document.createElement("div");
|
|
1501
2035
|
modalLayer.className = "dialog-layer";
|
|
1502
2036
|
modalLayer.dataset.role = "activation-layer";
|
|
@@ -1524,24 +2058,21 @@ class VoicePresenceView {
|
|
|
1524
2058
|
close.setAttribute("aria-label", this.voiceConfig.activation.secondaryLabel);
|
|
1525
2059
|
close.append(icon(lucide_1.X, "close-symbol"));
|
|
1526
2060
|
head.append(copy, close);
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
checkbox.type = "checkbox";
|
|
1531
|
-
checkbox.checked = true;
|
|
1532
|
-
checkbox.dataset.role = "agreement";
|
|
1533
|
-
const agreementText = document.createElement("span");
|
|
1534
|
-
agreementText.textContent = `I agree to use ${this.config.appearance?.brandName || "AI voice assist"}.`;
|
|
1535
|
-
agreement.append(checkbox, agreementText);
|
|
1536
|
-
if (this.voiceConfig.activation.detailsLabel) {
|
|
2061
|
+
const pageContextDisclosed = this.config.capabilities?.pageContext === true &&
|
|
2062
|
+
this.config.consent?.requiredScopes.includes("page_context") === true;
|
|
2063
|
+
if (this.voiceConfig.activation.detailsLabel || pageContextDisclosed) {
|
|
1537
2064
|
const details = document.createElement("details");
|
|
1538
2065
|
const summary = document.createElement("summary");
|
|
1539
|
-
summary.textContent =
|
|
2066
|
+
summary.textContent =
|
|
2067
|
+
this.voiceConfig.activation.detailsLabel || this.messages.legalDetails;
|
|
1540
2068
|
const legal = document.createElement("div");
|
|
1541
2069
|
legal.className = "legal-copy";
|
|
1542
2070
|
const disclosure = document.createElement("p");
|
|
1543
2071
|
disclosure.textContent =
|
|
1544
2072
|
this.config.consent?.disclosure || this.messages.disclosure;
|
|
2073
|
+
const pageContext = document.createElement("p");
|
|
2074
|
+
pageContext.textContent = this.messages.pageConsent;
|
|
2075
|
+
pageContext.hidden = !pageContextDisclosed;
|
|
1545
2076
|
const boundary = document.createElement("p");
|
|
1546
2077
|
boundary.textContent = this.messages.safetyBoundary;
|
|
1547
2078
|
const links = document.createElement("div");
|
|
@@ -1552,12 +2083,12 @@ class VoicePresenceView {
|
|
|
1552
2083
|
if (this.config.consent?.termsUrl) {
|
|
1553
2084
|
links.append(this.legalLink(this.config.consent.termsUrl, this.messages.termsOfService));
|
|
1554
2085
|
}
|
|
1555
|
-
legal.append(disclosure, boundary, links);
|
|
2086
|
+
legal.append(disclosure, pageContext, boundary, links);
|
|
1556
2087
|
details.append(summary, legal);
|
|
1557
|
-
dialog.append(head,
|
|
2088
|
+
dialog.append(head, details);
|
|
1558
2089
|
}
|
|
1559
2090
|
else {
|
|
1560
|
-
dialog.append(head
|
|
2091
|
+
dialog.append(head);
|
|
1561
2092
|
}
|
|
1562
2093
|
const actions = document.createElement("div");
|
|
1563
2094
|
actions.className = "dialog-actions";
|
|
@@ -1574,6 +2105,63 @@ class VoicePresenceView {
|
|
|
1574
2105
|
actions.append(secondary, primary);
|
|
1575
2106
|
dialog.append(actions);
|
|
1576
2107
|
modalLayer.append(dialog);
|
|
2108
|
+
const pageAssistanceLayer = document.createElement("div");
|
|
2109
|
+
pageAssistanceLayer.className = "dialog-layer";
|
|
2110
|
+
pageAssistanceLayer.dataset.role = "page-assistance-layer";
|
|
2111
|
+
pageAssistanceLayer.hidden = true;
|
|
2112
|
+
const pageAssistanceDialog = document.createElement("section");
|
|
2113
|
+
pageAssistanceDialog.className = "dialog page-assistance-dialog";
|
|
2114
|
+
pageAssistanceDialog.setAttribute("role", "alertdialog");
|
|
2115
|
+
pageAssistanceDialog.setAttribute("aria-modal", "true");
|
|
2116
|
+
pageAssistanceDialog.setAttribute("aria-labelledby", "oxvo-page-assistance-title");
|
|
2117
|
+
pageAssistanceDialog.setAttribute("aria-describedby", "oxvo-page-assistance-description oxvo-page-assistance-target");
|
|
2118
|
+
const pageAssistanceHead = document.createElement("div");
|
|
2119
|
+
pageAssistanceHead.className = "dialog-head";
|
|
2120
|
+
const pageAssistanceCopy = document.createElement("div");
|
|
2121
|
+
pageAssistanceCopy.className = "page-assistance-copy";
|
|
2122
|
+
const pageAssistanceTitle = document.createElement("h2");
|
|
2123
|
+
pageAssistanceTitle.id = "oxvo-page-assistance-title";
|
|
2124
|
+
pageAssistanceTitle.textContent = PAGE_ASSISTANCE_TITLE;
|
|
2125
|
+
const pageAssistanceDescription = document.createElement("p");
|
|
2126
|
+
pageAssistanceDescription.id = "oxvo-page-assistance-description";
|
|
2127
|
+
pageAssistanceDescription.dataset.role = "page-assistance-description";
|
|
2128
|
+
pageAssistanceDescription.textContent = PAGE_ASSISTANCE_FALLBACK;
|
|
2129
|
+
const pageAssistanceTarget = document.createElement("p");
|
|
2130
|
+
pageAssistanceTarget.id = "oxvo-page-assistance-target";
|
|
2131
|
+
pageAssistanceTarget.className = "page-assistance-target";
|
|
2132
|
+
pageAssistanceTarget.dataset.role = "page-assistance-target";
|
|
2133
|
+
pageAssistanceCopy.append(pageAssistanceTitle, pageAssistanceDescription, pageAssistanceTarget);
|
|
2134
|
+
const pageAssistanceClose = document.createElement("button");
|
|
2135
|
+
pageAssistanceClose.type = "button";
|
|
2136
|
+
pageAssistanceClose.className = "icon-button";
|
|
2137
|
+
pageAssistanceClose.dataset.role = "page-assistance-close";
|
|
2138
|
+
pageAssistanceClose.setAttribute("aria-label", PAGE_ASSISTANCE_DECLINE);
|
|
2139
|
+
pageAssistanceClose.append(icon(lucide_1.X, "close-symbol"));
|
|
2140
|
+
pageAssistanceHead.append(pageAssistanceCopy, pageAssistanceClose);
|
|
2141
|
+
const pageAssistanceAlways = document.createElement("label");
|
|
2142
|
+
pageAssistanceAlways.className = "page-assistance-always";
|
|
2143
|
+
pageAssistanceAlways.dataset.role = "page-assistance-always-label";
|
|
2144
|
+
const pageAssistanceCheckbox = document.createElement("input");
|
|
2145
|
+
pageAssistanceCheckbox.type = "checkbox";
|
|
2146
|
+
pageAssistanceCheckbox.dataset.role = "page-assistance-always";
|
|
2147
|
+
const pageAssistanceAlwaysText = document.createElement("span");
|
|
2148
|
+
pageAssistanceAlwaysText.textContent = PAGE_ASSISTANCE_ALWAYS;
|
|
2149
|
+
pageAssistanceAlways.append(pageAssistanceCheckbox, pageAssistanceAlwaysText);
|
|
2150
|
+
const pageAssistanceActions = document.createElement("div");
|
|
2151
|
+
pageAssistanceActions.className = "dialog-actions";
|
|
2152
|
+
const pageAssistanceDecline = document.createElement("button");
|
|
2153
|
+
pageAssistanceDecline.type = "button";
|
|
2154
|
+
pageAssistanceDecline.className = "button";
|
|
2155
|
+
pageAssistanceDecline.dataset.role = "page-assistance-decline";
|
|
2156
|
+
pageAssistanceDecline.textContent = PAGE_ASSISTANCE_DECLINE;
|
|
2157
|
+
const pageAssistanceAllow = document.createElement("button");
|
|
2158
|
+
pageAssistanceAllow.type = "button";
|
|
2159
|
+
pageAssistanceAllow.className = "button primary";
|
|
2160
|
+
pageAssistanceAllow.dataset.role = "page-assistance-allow";
|
|
2161
|
+
pageAssistanceAllow.textContent = PAGE_ASSISTANCE_ALLOW;
|
|
2162
|
+
pageAssistanceActions.append(pageAssistanceDecline, pageAssistanceAllow);
|
|
2163
|
+
pageAssistanceDialog.append(pageAssistanceHead, pageAssistanceAlways, pageAssistanceActions);
|
|
2164
|
+
pageAssistanceLayer.append(pageAssistanceDialog);
|
|
1577
2165
|
const live = document.createElement("div");
|
|
1578
2166
|
live.className = "hidden-live";
|
|
1579
2167
|
live.dataset.role = "live-region";
|
|
@@ -1586,7 +2174,7 @@ class VoicePresenceView {
|
|
|
1586
2174
|
const highlight = document.createElement("div");
|
|
1587
2175
|
highlight.className = "action-highlight";
|
|
1588
2176
|
highlight.setAttribute("aria-hidden", "true");
|
|
1589
|
-
fragment.append(presenceField, orbWrap, modalLayer, live, audio, highlight);
|
|
2177
|
+
fragment.append(presenceField, orbWrap, modalLayer, pageAssistanceLayer, live, audio, highlight);
|
|
1590
2178
|
return fragment;
|
|
1591
2179
|
}
|
|
1592
2180
|
bind() {
|
|
@@ -1602,27 +2190,55 @@ class VoicePresenceView {
|
|
|
1602
2190
|
});
|
|
1603
2191
|
this.require('[data-role="activation-close"]').addEventListener("click", () => this.closeActivation());
|
|
1604
2192
|
this.require('[data-role="activation-secondary"]').addEventListener("click", () => this.closeActivation());
|
|
1605
|
-
this.require('[data-role="activation-primary"]')
|
|
1606
|
-
|
|
1607
|
-
|
|
2193
|
+
const activationPrimary = this.require('[data-role="activation-primary"]');
|
|
2194
|
+
activationPrimary.addEventListener("click", () => {
|
|
2195
|
+
if (this.modalLayer.hidden || activationPrimary.disabled)
|
|
2196
|
+
return;
|
|
2197
|
+
activationPrimary.disabled = true;
|
|
2198
|
+
activationPrimary.setAttribute("aria-busy", "true");
|
|
2199
|
+
const requiredScopes = new Set(this.config.consent?.requiredScopes ?? []);
|
|
2200
|
+
const scopes = [
|
|
2201
|
+
"ai_disclosure",
|
|
2202
|
+
"text",
|
|
2203
|
+
"microphone",
|
|
2204
|
+
];
|
|
2205
|
+
for (const scope of [
|
|
2206
|
+
"page_context",
|
|
2207
|
+
"presentation_guidance",
|
|
2208
|
+
]) {
|
|
2209
|
+
if (requiredScopes.has(scope))
|
|
2210
|
+
scopes.push(scope);
|
|
2211
|
+
}
|
|
2212
|
+
if (!this.callbacks.onStart("voice", scopes)) {
|
|
2213
|
+
activationPrimary.disabled = false;
|
|
2214
|
+
activationPrimary.removeAttribute("aria-busy");
|
|
2215
|
+
activationPrimary.focus({ preventScroll: true });
|
|
1608
2216
|
return;
|
|
1609
2217
|
}
|
|
1610
2218
|
this.ensureAudioContext();
|
|
1611
2219
|
this.showPreflight();
|
|
1612
|
-
const consent = this.config.consent;
|
|
1613
|
-
const scopes = [
|
|
1614
|
-
...new Set([
|
|
1615
|
-
...(consent?.requiredScopes ?? []),
|
|
1616
|
-
...(consent?.optionalScopes ?? []),
|
|
1617
|
-
]),
|
|
1618
|
-
];
|
|
1619
|
-
this.callbacks.onStart("voice", scopes);
|
|
1620
2220
|
});
|
|
1621
|
-
this.modalLayer.addEventListener("keydown", this.onModalKeyDown);
|
|
1622
2221
|
this.modalLayer.addEventListener("pointerdown", (event) => {
|
|
1623
2222
|
if (event.target === this.modalLayer)
|
|
1624
2223
|
this.closeActivation();
|
|
1625
2224
|
});
|
|
2225
|
+
this.pageAssistanceLayer.addEventListener("pointerdown", (event) => {
|
|
2226
|
+
if (event.target === this.pageAssistanceLayer) {
|
|
2227
|
+
this.resolvePageAssistanceRequest("decline");
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
this.require('[data-role="page-assistance-close"]').addEventListener("click", () => this.resolvePageAssistanceRequest("decline"));
|
|
2231
|
+
this.require('[data-role="page-assistance-decline"]').addEventListener("click", () => this.resolvePageAssistanceRequest("decline"));
|
|
2232
|
+
this.require('[data-role="page-assistance-allow"]').addEventListener("click", () => {
|
|
2233
|
+
const persistent = this.currentPageAssistanceRequest?.persistenceEligible === true &&
|
|
2234
|
+
this.pageAssistanceAlways.checked;
|
|
2235
|
+
this.resolvePageAssistanceRequest(persistent ? "allow_persistent" : "allow_session");
|
|
2236
|
+
});
|
|
2237
|
+
this.pageAssistanceRevoke.addEventListener("click", () => {
|
|
2238
|
+
this.setPageAssistancePersistentAllowed(false);
|
|
2239
|
+
this.callbacks.onPageAssistanceRevoke?.();
|
|
2240
|
+
});
|
|
2241
|
+
this.require('[data-role="voice-support-action"]').addEventListener("click", () => this.callbacks.onHuman());
|
|
1626
2242
|
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
1627
2243
|
document.addEventListener("scroll", this.scheduleHighlightLayout, {
|
|
1628
2244
|
capture: true,
|
|
@@ -1687,9 +2303,10 @@ class VoicePresenceView {
|
|
|
1687
2303
|
state === "reconnecting") {
|
|
1688
2304
|
return `active.${state}`;
|
|
1689
2305
|
}
|
|
1690
|
-
if (this.playbackState === "playing")
|
|
1691
|
-
return "active.speaking";
|
|
1692
2306
|
if (state === "speaking") {
|
|
2307
|
+
if (this.playbackState === "playing") {
|
|
2308
|
+
return "active.speaking";
|
|
2309
|
+
}
|
|
1693
2310
|
return this.playbackState === "paused" || this.playbackState === "ended"
|
|
1694
2311
|
? "active.ready"
|
|
1695
2312
|
: "active.thinking";
|
|
@@ -1729,31 +2346,35 @@ class VoicePresenceView {
|
|
|
1729
2346
|
this.host.style.setProperty("--voice-orb-size", this.voiceConfig.launcher.size === "compact" ? "52px" : "58px");
|
|
1730
2347
|
}
|
|
1731
2348
|
openActivation() {
|
|
2349
|
+
if (!this.modalLayer.hidden)
|
|
2350
|
+
return;
|
|
1732
2351
|
this.hideAttribution();
|
|
1733
|
-
const shadowActiveElement = this.shadow.activeElement;
|
|
1734
|
-
const documentActiveElement = document.activeElement;
|
|
1735
|
-
this.restoreFocus =
|
|
1736
|
-
shadowActiveElement instanceof HTMLElement
|
|
1737
|
-
? shadowActiveElement
|
|
1738
|
-
: documentActiveElement instanceof HTMLElement &&
|
|
1739
|
-
documentActiveElement !== this.host
|
|
1740
|
-
? documentActiveElement
|
|
1741
|
-
: this.orb;
|
|
1742
2352
|
this.require('[data-role="first-label"]', false)?.remove();
|
|
1743
2353
|
this.placement.refresh(true);
|
|
2354
|
+
const primary = this.require('[data-role="activation-primary"]');
|
|
2355
|
+
primary.disabled = false;
|
|
2356
|
+
primary.removeAttribute("aria-busy");
|
|
1744
2357
|
this.modalLayer.hidden = false;
|
|
1745
2358
|
this.stateMachine.transition("activation_modal");
|
|
1746
|
-
|
|
2359
|
+
this.callbacks.onOpenChange?.(true);
|
|
2360
|
+
this.dialogFocus.present({
|
|
2361
|
+
surface: this.modalLayer,
|
|
2362
|
+
initialFocus: () => primary,
|
|
2363
|
+
fallbackFocus: () => (this.orb.hidden ? null : this.orb),
|
|
2364
|
+
onEscape: () => this.closeActivation(),
|
|
2365
|
+
});
|
|
1747
2366
|
}
|
|
1748
2367
|
closeActivation(restore = true) {
|
|
1749
2368
|
if (this.modalLayer.hidden)
|
|
1750
2369
|
return;
|
|
1751
2370
|
this.modalLayer.hidden = true;
|
|
2371
|
+
const primary = this.require('[data-role="activation-primary"]');
|
|
2372
|
+
primary.disabled = false;
|
|
2373
|
+
primary.removeAttribute("aria-busy");
|
|
1752
2374
|
if (this.stateMachine.state === "activation_modal")
|
|
1753
2375
|
this.stateMachine.transition("launcher");
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
this.restoreFocus = null;
|
|
2376
|
+
this.callbacks.onOpenChange?.(false);
|
|
2377
|
+
this.dialogFocus.dismiss(this.modalLayer, { restore });
|
|
1757
2378
|
this.placement.refresh();
|
|
1758
2379
|
}
|
|
1759
2380
|
showAttribution() {
|
|
@@ -1772,12 +2393,46 @@ class VoicePresenceView {
|
|
|
1772
2393
|
this.attributionTimer = null;
|
|
1773
2394
|
this.attributionBubble.hidden = true;
|
|
1774
2395
|
}
|
|
2396
|
+
resolvePageAssistanceRequest(decision, restore = true) {
|
|
2397
|
+
const request = this.currentPageAssistanceRequest;
|
|
2398
|
+
if (!request)
|
|
2399
|
+
return;
|
|
2400
|
+
const normalizedDecision = decision === "allow_persistent" && !request.persistenceEligible
|
|
2401
|
+
? "allow_session"
|
|
2402
|
+
: decision;
|
|
2403
|
+
this.hidePageAssistanceRequest(restore);
|
|
2404
|
+
this.callbacks.onPageAssistance?.(request, normalizedDecision);
|
|
2405
|
+
}
|
|
2406
|
+
hidePageAssistanceRequest(restore) {
|
|
2407
|
+
if (!this.currentPageAssistanceRequest && this.pageAssistanceLayer.hidden) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
const wasOpen = !this.pageAssistanceLayer.hidden;
|
|
2411
|
+
this.currentPageAssistanceRequest = null;
|
|
2412
|
+
this.pageAssistanceAlways.checked = false;
|
|
2413
|
+
this.pageAssistanceLayer.hidden = true;
|
|
2414
|
+
this.syncPageAssistanceRevoke();
|
|
2415
|
+
this.syncSubtitleOcclusion();
|
|
2416
|
+
if (this.active && this.stateMachine.state === "active.awaiting_confirmation") {
|
|
2417
|
+
this.stateMachine.transition("active.ready");
|
|
2418
|
+
}
|
|
2419
|
+
if (wasOpen)
|
|
2420
|
+
this.callbacks.onOpenChange?.(false);
|
|
2421
|
+
this.dialogFocus.dismiss(this.pageAssistanceLayer, { restore });
|
|
2422
|
+
}
|
|
2423
|
+
syncPageAssistanceRevoke() {
|
|
2424
|
+
this.pageAssistanceRevoke.hidden =
|
|
2425
|
+
!this.pageAssistancePersistentAllowed || !this.pageAssistanceLayer.hidden;
|
|
2426
|
+
}
|
|
1775
2427
|
renderSafetyCard(title, copy, approveLabel, declineLabel, approve, decline) {
|
|
2428
|
+
this.restoreSafetyDialogAttributes();
|
|
1776
2429
|
const heading = document.createElement("h3");
|
|
1777
2430
|
heading.className = "card-title";
|
|
2431
|
+
heading.id = "oxvo-voice-safety-title";
|
|
1778
2432
|
heading.textContent = title.slice(0, 160);
|
|
1779
2433
|
const body = document.createElement("p");
|
|
1780
2434
|
body.className = "card-copy";
|
|
2435
|
+
body.id = "oxvo-voice-safety-copy";
|
|
1781
2436
|
body.textContent = copy.slice(0, 600);
|
|
1782
2437
|
const actions = document.createElement("div");
|
|
1783
2438
|
actions.className = "card-actions";
|
|
@@ -1797,21 +2452,43 @@ class VoicePresenceView {
|
|
|
1797
2452
|
this.safetyCard.replaceChildren(heading, body, actions);
|
|
1798
2453
|
this.safetyCard.hidden = false;
|
|
1799
2454
|
this.syncSubtitleOcclusion();
|
|
1800
|
-
|
|
2455
|
+
this.dialogFocus.present({
|
|
2456
|
+
surface: this.safetyCard,
|
|
2457
|
+
initialFocus: () => no,
|
|
2458
|
+
fallbackFocus: () => (this.orb.hidden ? null : this.orb),
|
|
2459
|
+
onEscape: decline,
|
|
2460
|
+
});
|
|
1801
2461
|
}
|
|
1802
|
-
hideSafetyCard() {
|
|
2462
|
+
hideSafetyCard(restore = true) {
|
|
2463
|
+
this.handoffRequestPreparing = false;
|
|
1803
2464
|
this.safetyCard.hidden = true;
|
|
1804
2465
|
this.safetyCard.replaceChildren();
|
|
2466
|
+
this.restoreSafetyDialogAttributes();
|
|
2467
|
+
this.dialogFocus.dismiss(this.safetyCard, { restore });
|
|
2468
|
+
if (!restore)
|
|
2469
|
+
this.dialogFocus.clear({ restore: false });
|
|
1805
2470
|
this.syncSubtitleOcclusion();
|
|
1806
2471
|
}
|
|
2472
|
+
restoreSafetyDialogAttributes() {
|
|
2473
|
+
this.safetyCard.setAttribute("role", "alertdialog");
|
|
2474
|
+
this.safetyCard.setAttribute("aria-modal", "true");
|
|
2475
|
+
this.safetyCard.setAttribute("aria-labelledby", "oxvo-voice-safety-title");
|
|
2476
|
+
this.safetyCard.setAttribute("aria-describedby", "oxvo-voice-safety-copy");
|
|
2477
|
+
this.safetyCard.removeAttribute("aria-live");
|
|
2478
|
+
this.safetyCard.removeAttribute("aria-busy");
|
|
2479
|
+
}
|
|
1807
2480
|
resolveConfirmation(approved) {
|
|
1808
2481
|
const confirmation = this.currentConfirmation;
|
|
1809
2482
|
if (!confirmation)
|
|
1810
2483
|
return;
|
|
2484
|
+
const accepted = approved && Date.parse(confirmation.expiresAt) > Date.now();
|
|
1811
2485
|
this.currentConfirmation = null;
|
|
1812
2486
|
this.hideSafetyCard();
|
|
1813
2487
|
this.stateMachine.transition("active.ready");
|
|
1814
|
-
|
|
2488
|
+
if (approved && !accepted) {
|
|
2489
|
+
this.showTransientStatus(this.messages.actionFailed, 3500);
|
|
2490
|
+
}
|
|
2491
|
+
this.callbacks.onConfirmation(confirmation.actionId, confirmation.proposalChecksum, accepted);
|
|
1815
2492
|
}
|
|
1816
2493
|
resolveVisualRequest(approved) {
|
|
1817
2494
|
const request = this.currentVisualRequest;
|
|
@@ -1822,7 +2499,20 @@ class VoicePresenceView {
|
|
|
1822
2499
|
this.stateMachine.transition("active.ready");
|
|
1823
2500
|
this.callbacks.onVisualContext(request, approved);
|
|
1824
2501
|
}
|
|
1825
|
-
|
|
2502
|
+
resolveHandoffContextRequest(approved) {
|
|
2503
|
+
const request = this.currentHandoffContextRequest;
|
|
2504
|
+
if (!request)
|
|
2505
|
+
return;
|
|
2506
|
+
this.currentHandoffContextRequest = null;
|
|
2507
|
+
this.hideSafetyCard();
|
|
2508
|
+
if (this.active)
|
|
2509
|
+
this.stateMachine.transition("active.ready");
|
|
2510
|
+
if (approved) {
|
|
2511
|
+
this.showTransientStatus(this.messages.humanConnecting, 3500);
|
|
2512
|
+
}
|
|
2513
|
+
this.callbacks.onHandoffContext?.(request, approved);
|
|
2514
|
+
}
|
|
2515
|
+
renderRecoveryCard(message, retryable, primaryAction) {
|
|
1826
2516
|
const badge = document.createElement("div");
|
|
1827
2517
|
badge.className = "recovery-badge";
|
|
1828
2518
|
badge.setAttribute("aria-hidden", "true");
|
|
@@ -1844,21 +2534,27 @@ class VoicePresenceView {
|
|
|
1844
2534
|
close.setAttribute("aria-label", this.messages.close);
|
|
1845
2535
|
close.append(icon(lucide_1.X, "close-symbol"));
|
|
1846
2536
|
close.addEventListener("click", () => {
|
|
1847
|
-
this.
|
|
1848
|
-
this.recoveryCard.replaceChildren();
|
|
1849
|
-
this.syncSubtitleOcclusion();
|
|
2537
|
+
this.hideRecoveryCard();
|
|
1850
2538
|
});
|
|
1851
2539
|
const heading = document.createElement("div");
|
|
1852
2540
|
heading.className = "recovery-head";
|
|
1853
2541
|
heading.append(badge, title, close);
|
|
1854
|
-
|
|
2542
|
+
let primary = null;
|
|
2543
|
+
if (primaryAction) {
|
|
2544
|
+
primary = document.createElement("button");
|
|
2545
|
+
primary.type = "button";
|
|
2546
|
+
primary.className = "button primary";
|
|
2547
|
+
primary.textContent = primaryAction.label;
|
|
2548
|
+
primary.addEventListener("click", primaryAction.onSelect);
|
|
2549
|
+
actions.append(primary);
|
|
2550
|
+
}
|
|
2551
|
+
else if (retryable) {
|
|
1855
2552
|
const retry = document.createElement("button");
|
|
1856
2553
|
retry.type = "button";
|
|
1857
2554
|
retry.className = "button primary";
|
|
1858
2555
|
retry.append(icon(lucide_1.RefreshCw, "retry-symbol"), document.createTextNode(this.messages.retry));
|
|
1859
2556
|
retry.addEventListener("click", () => {
|
|
1860
|
-
this.
|
|
1861
|
-
this.syncSubtitleOcclusion();
|
|
2557
|
+
this.hideRecoveryCard();
|
|
1862
2558
|
this.callbacks.onRetry();
|
|
1863
2559
|
});
|
|
1864
2560
|
actions.append(retry);
|
|
@@ -1875,9 +2571,7 @@ class VoicePresenceView {
|
|
|
1875
2571
|
: this.messages.requestHumanFallback;
|
|
1876
2572
|
fallbackButton.addEventListener("click", () => {
|
|
1877
2573
|
fallbackButton.disabled = true;
|
|
1878
|
-
this.
|
|
1879
|
-
this.recoveryCard.replaceChildren();
|
|
1880
|
-
this.syncSubtitleOcclusion();
|
|
2574
|
+
this.hideRecoveryCard();
|
|
1881
2575
|
this.onFallback(fallback);
|
|
1882
2576
|
}, { once: true });
|
|
1883
2577
|
actions.append(fallbackButton);
|
|
@@ -1889,9 +2583,27 @@ class VoicePresenceView {
|
|
|
1889
2583
|
this.recoveryCard.append(actions);
|
|
1890
2584
|
this.recoveryCard.hidden = false;
|
|
1891
2585
|
this.syncSubtitleOcclusion();
|
|
2586
|
+
this.dialogFocus.present({
|
|
2587
|
+
surface: this.recoveryCard,
|
|
2588
|
+
initialFocus: () => primary ?? close,
|
|
2589
|
+
fallbackFocus: () => (this.orb.hidden ? null : this.orb),
|
|
2590
|
+
onEscape: () => {
|
|
2591
|
+
this.hideRecoveryCard();
|
|
2592
|
+
},
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
hideRecoveryCard(restore = true) {
|
|
2596
|
+
this.recoveryCard.hidden = true;
|
|
2597
|
+
this.recoveryCard.replaceChildren();
|
|
2598
|
+
this.dialogFocus.dismiss(this.recoveryCard, { restore });
|
|
2599
|
+
if (!restore)
|
|
2600
|
+
this.dialogFocus.clear({ restore: false });
|
|
2601
|
+
this.syncSubtitleOcclusion();
|
|
1892
2602
|
}
|
|
1893
2603
|
syncSubtitleOcclusion() {
|
|
1894
|
-
const occluded = !this.safetyCard.hidden ||
|
|
2604
|
+
const occluded = !this.safetyCard.hidden ||
|
|
2605
|
+
!this.recoveryCard.hidden ||
|
|
2606
|
+
!this.pageAssistanceLayer.hidden;
|
|
1895
2607
|
this.captionStage.hidden = occluded;
|
|
1896
2608
|
if (occluded) {
|
|
1897
2609
|
this.host.style.setProperty("--voice-subtitle-height", "0px");
|
|
@@ -2095,6 +2807,7 @@ class VoicePresenceView {
|
|
|
2095
2807
|
}
|
|
2096
2808
|
startLevelLoop() {
|
|
2097
2809
|
if (this.levelFrame !== null ||
|
|
2810
|
+
!this.motionEnabled() ||
|
|
2098
2811
|
!this.activeAnalyser() ||
|
|
2099
2812
|
!this.levelBuffer ||
|
|
2100
2813
|
document.hidden) {
|
|
@@ -2103,7 +2816,10 @@ class VoicePresenceView {
|
|
|
2103
2816
|
const frame = (timestamp) => {
|
|
2104
2817
|
this.levelFrame = null;
|
|
2105
2818
|
const analyser = this.activeAnalyser();
|
|
2106
|
-
if (!this.active ||
|
|
2819
|
+
if (!this.active ||
|
|
2820
|
+
!this.motionEnabled() ||
|
|
2821
|
+
!analyser ||
|
|
2822
|
+
!this.levelBuffer) {
|
|
2107
2823
|
this.host.style.setProperty("--voice-level", "0");
|
|
2108
2824
|
return;
|
|
2109
2825
|
}
|
|
@@ -2152,6 +2868,28 @@ class VoicePresenceView {
|
|
|
2152
2868
|
void this.audioContext.close().catch(() => undefined);
|
|
2153
2869
|
this.audioContext = null;
|
|
2154
2870
|
}
|
|
2871
|
+
motionEnabled() {
|
|
2872
|
+
return (this.capabilityTier === "standard" &&
|
|
2873
|
+
this.voiceConfig.visual.lowPowerMode !== "always" &&
|
|
2874
|
+
!this.isReducedMotionRequested() &&
|
|
2875
|
+
!document.hidden);
|
|
2876
|
+
}
|
|
2877
|
+
isReducedMotionRequested() {
|
|
2878
|
+
return (this.reducedMotionQuery?.matches === true ||
|
|
2879
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
|
|
2880
|
+
}
|
|
2881
|
+
syncMotionPolicy() {
|
|
2882
|
+
const staticMotion = this.capabilityTier !== "standard" ||
|
|
2883
|
+
this.voiceConfig.visual.lowPowerMode === "always" ||
|
|
2884
|
+
this.isReducedMotionRequested();
|
|
2885
|
+
this.host.dataset.motion = staticMotion ? "off" : "on";
|
|
2886
|
+
this.host.dataset.effectsPaused = String(staticMotion || document.hidden);
|
|
2887
|
+
if (staticMotion || document.hidden) {
|
|
2888
|
+
this.stopLevelLoop();
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
this.syncLevelLoop();
|
|
2892
|
+
}
|
|
2155
2893
|
activeAnalyser() {
|
|
2156
2894
|
if (this.stateMachine.state === "active.listening")
|
|
2157
2895
|
return this.inputAnalyser;
|