@vanira/sdk-react-native 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -0
- package/package.json +53 -0
- package/src/__tests__/WebRTCClient.integration.test.ts +396 -0
- package/src/__tests__/adapters.test.ts +475 -0
- package/src/__tests__/httpResponse.test.ts +25 -0
- package/src/__tests__/mocks/react-native-incall-manager.ts +8 -0
- package/src/__tests__/mocks/react-native-permissions.ts +15 -0
- package/src/__tests__/mocks/react-native-webrtc.ts +6 -0
- package/src/__tests__/mocks/react-native.ts +28 -0
- package/src/__tests__/preset.test.ts +239 -0
- package/src/__tests__/resolveRuntimeConfig.test.ts +90 -0
- package/src/__tests__/storage.test.ts +211 -0
- package/src/__tests__/webrtcSignaling.test.ts +42 -0
- package/src/adapters/PeerConnectionAdapter.ts +101 -0
- package/src/adapters/browser/BrowserAudioAdapter.ts +43 -0
- package/src/adapters/browser/BrowserDataChannelAdapter.ts +69 -0
- package/src/adapters/browser/BrowserMediaAdapter.ts +15 -0
- package/src/adapters/browser/BrowserPeerAdapter.ts +14 -0
- package/src/adapters/browser/index.ts +4 -0
- package/src/adapters/interfaces.ts +84 -0
- package/src/adapters/react-native/RNAudioAdapter.ts +42 -0
- package/src/adapters/react-native/RNDataChannelAdapter.ts +79 -0
- package/src/adapters/react-native/RNMediaAdapter.ts +46 -0
- package/src/adapters/react-native/RNPeerAdapter.ts +28 -0
- package/src/adapters/react-native/callAudioRouting.ts +115 -0
- package/src/adapters/react-native/decodeUtf8.ts +72 -0
- package/src/adapters/react-native/index.ts +4 -0
- package/src/adapters/react-native/rnUploadFile.ts +76 -0
- package/src/adapters/storage/BrowserDualStorageAdapter.ts +71 -0
- package/src/adapters/storage/MemoryStorageAdapter.ts +50 -0
- package/src/adapters/storage/StorageAdapter.ts +21 -0
- package/src/adapters/storage/createSyncStorageAdapter.ts +40 -0
- package/src/adapters/storage/index.ts +7 -0
- package/src/api/services/ChatService.ts +304 -0
- package/src/api/services/ConfigService.ts +33 -0
- package/src/assets/icons.js +35 -0
- package/src/cdn.ts +68 -0
- package/src/core/CallSessionStore.ts +137 -0
- package/src/core/DraggableController.ts +83 -0
- package/src/core/SessionManager.ts +322 -0
- package/src/core/VaniraAI.ts +464 -0
- package/src/core/WebRTCClient.ts +1012 -0
- package/src/core/httpResponse.ts +22 -0
- package/src/core/iceServers.ts +18 -0
- package/src/core/toolCallNormalize.ts +80 -0
- package/src/core/voice-client.js +236 -0
- package/src/core/webrtcSignaling.ts +72 -0
- package/src/index.js +34 -0
- package/src/index.ts +6 -0
- package/src/platforms/browser.ts +67 -0
- package/src/platforms/react-native.ts +105 -0
- package/src/presets/BookingCalendarModal.tsx +457 -0
- package/src/presets/CameraModal.tsx +576 -0
- package/src/presets/DynamicFormModal.tsx +378 -0
- package/src/presets/NativePresetRenderer.tsx +350 -0
- package/src/presets/NavigateHandler.tsx +75 -0
- package/src/presets/PresetHost.tsx +155 -0
- package/src/presets/PresetShellModal.tsx +97 -0
- package/src/presets/UploadModal.tsx +321 -0
- package/src/presets/calendar/calendarUtils.ts +386 -0
- package/src/presets/call/CallSpeakerToggle.tsx +59 -0
- package/src/presets/call/callAudioRouting.ts +2 -0
- package/src/presets/call/useCallSpeaker.ts +31 -0
- package/src/presets/camera/cameraPermissions.ts +18 -0
- package/src/presets/camera/cameraStream.ts +19 -0
- package/src/presets/camera/cameraUtils.ts +21 -0
- package/src/presets/camera/useLivenessFlow.ts +95 -0
- package/src/presets/chalkboard/ChalkboardOverlay.tsx +156 -0
- package/src/presets/chalkboard/EraseTextHandler.tsx +95 -0
- package/src/presets/chalkboard/TypeTextHandler.tsx +107 -0
- package/src/presets/chalkboard/boardAbort.ts +36 -0
- package/src/presets/chalkboard/boardQueue.ts +620 -0
- package/src/presets/chalkboard/chalkboardSession.ts +75 -0
- package/src/presets/chalkboard/drawUtils.ts +123 -0
- package/src/presets/chalkboard/textUtils.ts +109 -0
- package/src/presets/clipRegion/ClipRegionModal.tsx +261 -0
- package/src/presets/clipRegion/clipRegionBridge.ts +19 -0
- package/src/presets/form/formValidation.ts +104 -0
- package/src/presets/form/parseFormFields.ts +171 -0
- package/src/presets/host/HostElementPresetHandler.tsx +155 -0
- package/src/presets/host/hostPresetBridge.ts +71 -0
- package/src/presets/index.ts +63 -0
- package/src/presets/liveScreen/CloseLiveScreenHandler.tsx +36 -0
- package/src/presets/liveScreen/LiveScreenCaptureHost.tsx +312 -0
- package/src/presets/liveScreen/LiveScreenHandler.tsx +25 -0
- package/src/presets/liveScreen/LiveScreenPipOverlay.tsx +6 -0
- package/src/presets/liveScreen/liveScreenSession.ts +73 -0
- package/src/presets/liveVision/CloseLiveVisionHandler.tsx +29 -0
- package/src/presets/liveVision/LiveVisionCameraHost.tsx +317 -0
- package/src/presets/liveVision/LiveVisionHandler.tsx +26 -0
- package/src/presets/liveVision/LiveVisionPipOverlay.tsx +7 -0
- package/src/presets/liveVision/liveVisionFrameLoop.ts +38 -0
- package/src/presets/liveVision/liveVisionSession.ts +75 -0
- package/src/presets/liveVision/liveVisionUpload.ts +62 -0
- package/src/presets/navigation/internalRouteRegistry.ts +25 -0
- package/src/presets/navigation/navigationBridge.ts +76 -0
- package/src/presets/navigation/navigationTypes.ts +12 -0
- package/src/presets/parseToolCall.ts +60 -0
- package/src/presets/presetClientAdapter.ts +29 -0
- package/src/presets/presetCompletion.ts +91 -0
- package/src/presets/presetEventHelpers.ts +45 -0
- package/src/presets/registry.ts +128 -0
- package/src/presets/streaming/mediaFrameUpload.ts +93 -0
- package/src/presets/types.ts +74 -0
- package/src/presets/upload/pickUploadFile.ts +256 -0
- package/src/presets/upload/uploadFormats.ts +163 -0
- package/src/presets/upload/uploadUtils.ts +68 -0
- package/src/react/PresetRenderer.tsx +144 -0
- package/src/react/index.ts +1 -0
- package/src/runtime/browserRuntime.ts +54 -0
- package/src/runtime/platform.ts +17 -0
- package/src/runtime/reactNativeRuntime.ts +68 -0
- package/src/runtime/resolveRuntimeConfig.ts +75 -0
- package/src/runtime/runtimeBundles.ts +74 -0
- package/src/runtime/types.ts +135 -0
- package/src/types/react-native-incall-manager.d.ts +17 -0
- package/src/types/react-native-webrtc.d.ts +47 -0
- package/src/types.ts +133 -0
- package/src/ui/VaniraWidget.ts +87 -0
- package/src/ui/abstraction/AbstractWidgetProvider.ts +18 -0
- package/src/ui/abstraction/interfaces.ts +12 -0
- package/src/ui/adapters/VaniraChatAdapter.ts +42 -0
- package/src/ui/components/AvatarView.ts +81 -0
- package/src/ui/components/ChatWindow.ts +263 -0
- package/src/ui/components/FloatingButton.ts +163 -0
- package/src/ui/components/FloatingWelcomeChips.ts +137 -0
- package/src/ui/components/Panel.ts +120 -0
- package/src/ui/components/VoiceOrb.ts +79 -0
- package/src/ui/components/VoiceOverlay.ts +497 -0
- package/src/ui/components/index.ts +7 -0
- package/src/ui/factory/WidgetFactory.ts +16 -0
- package/src/ui/icons_data.ts +2 -0
- package/src/ui/presets/WidgetPresetRenderer.ts +1802 -0
- package/src/ui/presets/types.ts +16 -0
- package/src/ui/providers/VaniraInternalProvider.ts +1066 -0
- package/src/ui/styles/index.ts +323 -0
- package/src/ui/styles/keyframes.ts +76 -0
- package/src/ui/styles/theme.ts +57 -0
- package/src/ui/styles/widget.css.ts +838 -0
- package/src/ui/utils.ts +37 -0
- package/src/ui/views/AbstractChatView.ts +93 -0
- package/src/ui/views/AbstractVoiceView.ts +57 -0
- package/src/ui/views/AvatarOnlyView.ts +78 -0
- package/src/ui/views/ChatAvatarView.ts +66 -0
- package/src/ui/views/ChatOnlyView.ts +28 -0
- package/src/ui/views/ChatVoiceView.ts +15 -0
- package/src/ui/views/VoiceOnlyView.ts +25 -0
- package/src/ui/views/index.ts +5 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
|
|
2
|
+
export interface WelcomeChip {
|
|
3
|
+
title: string;
|
|
4
|
+
payload?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class FloatingWelcomeChips {
|
|
8
|
+
private element: HTMLElement;
|
|
9
|
+
private chips: WelcomeChip[] = [];
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private onChipClick: (text: string) => void
|
|
13
|
+
) {
|
|
14
|
+
this.element = document.createElement('div');
|
|
15
|
+
this.element.className = 'welcome-chips-container';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public setChips(chips: WelcomeChip[]) {
|
|
19
|
+
this.chips = chips;
|
|
20
|
+
this.render();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getElement(): HTMLElement {
|
|
24
|
+
return this.element;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public setPosition(positionStyle: string) {
|
|
28
|
+
this.element.style.cssText = ''; // Clear previous
|
|
29
|
+
const styles = positionStyle.split(';').filter(s => s.trim());
|
|
30
|
+
styles.forEach(s => {
|
|
31
|
+
const [prop, val] = s.split(':').map(str => str.trim());
|
|
32
|
+
if (prop && val) (this.element.style as any)[prop] = val;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Offset from FAB
|
|
36
|
+
if (this.element.style.bottom) {
|
|
37
|
+
this.element.style.bottom = (parseInt(this.element.style.bottom) + 76) + 'px';
|
|
38
|
+
} else if (this.element.style.top) {
|
|
39
|
+
this.element.style.top = (parseInt(this.element.style.top) + 76) + 'px';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.element.style.position = 'fixed';
|
|
43
|
+
this.element.style.zIndex = '2147483646';
|
|
44
|
+
this.element.style.alignItems = this.element.style.left ? 'flex-start' : 'flex-end';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public updateCoordinates(x: number, y: number, isBottom: boolean = true) {
|
|
48
|
+
this.element.style.left = `${x}px`;
|
|
49
|
+
if (isBottom) {
|
|
50
|
+
// Position ABOVE the fab (fab is around 60px)
|
|
51
|
+
this.element.style.top = 'auto';
|
|
52
|
+
this.element.style.bottom = `${window.innerHeight - y + 16}px`;
|
|
53
|
+
} else {
|
|
54
|
+
// Position BELOW the fab
|
|
55
|
+
this.element.style.bottom = 'auto';
|
|
56
|
+
this.element.style.top = `${y + 76}px`;
|
|
57
|
+
}
|
|
58
|
+
this.element.style.right = 'auto';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private render() {
|
|
62
|
+
this.element.innerHTML = '';
|
|
63
|
+
|
|
64
|
+
this.chips.forEach((chip, index) => {
|
|
65
|
+
const btn = document.createElement('button');
|
|
66
|
+
btn.className = 'welcome-chip-btn';
|
|
67
|
+
btn.textContent = chip.title;
|
|
68
|
+
btn.onclick = (e) => {
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
this.onChipClick(chip.title); // Send title as text
|
|
71
|
+
};
|
|
72
|
+
// Add staggered delay
|
|
73
|
+
btn.style.animationDelay = `${index * 0.1}s`;
|
|
74
|
+
this.element.appendChild(btn);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public static get styles() {
|
|
79
|
+
return `
|
|
80
|
+
.welcome-chips-container {
|
|
81
|
+
position: fixed;
|
|
82
|
+
z-index: 9998;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column; /* Stack vertically */
|
|
85
|
+
align-items: flex-end; /* Align to right (usually) */
|
|
86
|
+
gap: 12px;
|
|
87
|
+
pointer-events: none; /* Container passes clicks */
|
|
88
|
+
/* Max width to prevent huge buttons */
|
|
89
|
+
max-width: 300px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Adjust alignment based on position */
|
|
93
|
+
/* We might need to know if it's left or right to align flex-start or flex-end
|
|
94
|
+
For now, defaulting to flex-end (right) which matches most widgets.
|
|
95
|
+
TODO: Pass alignment if needed.
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
.welcome-chip-btn {
|
|
99
|
+
background: white;
|
|
100
|
+
color: #4f46e5; /* Secondary/Primary color - hardcoded or use var */
|
|
101
|
+
color: var(--primary);
|
|
102
|
+
border: 1px solid transparent;
|
|
103
|
+
padding: 10px 18px;
|
|
104
|
+
border-radius: 20px;
|
|
105
|
+
font-family: inherit;
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
110
|
+
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
111
|
+
pointer-events: auto;
|
|
112
|
+
opacity: 0;
|
|
113
|
+
margin-bottom: 4px; /* Tiny spacing */
|
|
114
|
+
|
|
115
|
+
/* Animation */
|
|
116
|
+
animation: slideUpFade 0.4s ease-out forwards;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.welcome-chip-btn:hover {
|
|
120
|
+
transform: translateY(-2px);
|
|
121
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
|
122
|
+
background: #f8fafc;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@keyframes slideUpFade {
|
|
126
|
+
from {
|
|
127
|
+
opacity: 0;
|
|
128
|
+
transform: translateY(20px);
|
|
129
|
+
}
|
|
130
|
+
to {
|
|
131
|
+
opacity: 1;
|
|
132
|
+
transform: translateY(0);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
|
|
2
|
+
import { icons } from '../styles';
|
|
3
|
+
|
|
4
|
+
export class Panel {
|
|
5
|
+
private element: HTMLDivElement;
|
|
6
|
+
private header: HTMLDivElement;
|
|
7
|
+
private titleElement: HTMLHeadingElement;
|
|
8
|
+
private contentArea: HTMLDivElement;
|
|
9
|
+
private maximizeBtn: HTMLButtonElement;
|
|
10
|
+
private closeBtn: HTMLButtonElement;
|
|
11
|
+
|
|
12
|
+
private isMaximized = false;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private onClose: () => void,
|
|
16
|
+
private title: string = 'Assistant'
|
|
17
|
+
) {
|
|
18
|
+
this.element = document.createElement('div');
|
|
19
|
+
this.element.className = 'widget-panel hidden';
|
|
20
|
+
|
|
21
|
+
// Header
|
|
22
|
+
this.header = document.createElement('div');
|
|
23
|
+
this.header.className = 'widget-header';
|
|
24
|
+
|
|
25
|
+
this.titleElement = document.createElement('h3');
|
|
26
|
+
this.titleElement.className = 'widget-title';
|
|
27
|
+
this.titleElement.textContent = this.title;
|
|
28
|
+
this.header.appendChild(this.titleElement);
|
|
29
|
+
|
|
30
|
+
const controls = document.createElement('div');
|
|
31
|
+
controls.style.display = 'flex';
|
|
32
|
+
controls.style.alignItems = 'center';
|
|
33
|
+
controls.style.gap = '8px';
|
|
34
|
+
controls.style.marginLeft = 'auto';
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
this.maximizeBtn = document.createElement('button');
|
|
39
|
+
this.maximizeBtn.className = 'close-btn maximize-btn';
|
|
40
|
+
this.maximizeBtn.innerHTML = icons.maximize2;
|
|
41
|
+
this.maximizeBtn.onclick = () => this.toggleMaximize();
|
|
42
|
+
|
|
43
|
+
this.closeBtn = document.createElement('button');
|
|
44
|
+
this.closeBtn.className = 'close-btn';
|
|
45
|
+
this.closeBtn.innerHTML = icons.close;
|
|
46
|
+
this.closeBtn.onclick = this.onClose;
|
|
47
|
+
|
|
48
|
+
controls.appendChild(this.maximizeBtn);
|
|
49
|
+
controls.appendChild(this.closeBtn);
|
|
50
|
+
|
|
51
|
+
this.header.appendChild(controls);
|
|
52
|
+
|
|
53
|
+
// Content Area
|
|
54
|
+
this.contentArea = document.createElement('div');
|
|
55
|
+
this.contentArea.className = 'widget-body';
|
|
56
|
+
this.contentArea.style.flex = '1';
|
|
57
|
+
this.contentArea.style.display = 'flex';
|
|
58
|
+
this.contentArea.style.flexDirection = 'column';
|
|
59
|
+
this.contentArea.style.overflow = 'hidden';
|
|
60
|
+
this.contentArea.style.padding = '0'; // Let children handle padding if needed
|
|
61
|
+
|
|
62
|
+
this.element.appendChild(this.header);
|
|
63
|
+
this.element.appendChild(this.contentArea);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getElement(): HTMLDivElement {
|
|
67
|
+
return this.element;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public setContent(content: HTMLElement) {
|
|
71
|
+
this.contentArea.innerHTML = '';
|
|
72
|
+
this.contentArea.appendChild(content);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public addToContent(element: HTMLElement) {
|
|
76
|
+
this.contentArea.appendChild(element);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public setTitle(title: string) {
|
|
80
|
+
this.titleElement.textContent = title;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public open(position: { bottom?: string, right?: string, top?: string, left?: string }) {
|
|
84
|
+
this.element.classList.remove('hidden');
|
|
85
|
+
this.updatePosition(position);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public updatePosition(position: { bottom?: string, right?: string, top?: string, left?: string }) {
|
|
89
|
+
// Reset positioning first to avoid conflicts (e.g. top vs bottom)
|
|
90
|
+
this.element.style.top = 'auto';
|
|
91
|
+
this.element.style.bottom = 'auto';
|
|
92
|
+
this.element.style.left = 'auto';
|
|
93
|
+
this.element.style.right = 'auto';
|
|
94
|
+
|
|
95
|
+
Object.assign(this.element.style, position);
|
|
96
|
+
this.element.style.position = 'fixed';
|
|
97
|
+
this.element.style.zIndex = '2147483647';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public close() {
|
|
101
|
+
this.element.classList.add('hidden');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public toggleMaximize() {
|
|
105
|
+
this.isMaximized = !this.isMaximized;
|
|
106
|
+
if (this.isMaximized) {
|
|
107
|
+
this.element.classList.add('maximized');
|
|
108
|
+
this.maximizeBtn.innerHTML = icons.minimize2;
|
|
109
|
+
} else {
|
|
110
|
+
this.element.classList.remove('maximized');
|
|
111
|
+
this.maximizeBtn.innerHTML = icons.maximize2;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public static get styles() {
|
|
116
|
+
return `
|
|
117
|
+
/* Panel Styles handled in widget.css.ts mostly, specific overrides here */
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { icons } from '../styles';
|
|
2
|
+
|
|
3
|
+
export class VoiceOrb {
|
|
4
|
+
private element: HTMLDivElement;
|
|
5
|
+
private showSessionSelector = false;
|
|
6
|
+
|
|
7
|
+
constructor(private onClick: (behavior: 'continue' | 'new') => void) {
|
|
8
|
+
this.element = document.createElement('div');
|
|
9
|
+
this.element.className = 'voice-mode-container';
|
|
10
|
+
|
|
11
|
+
// Check if there is an active saved call session in sessionStorage or localStorage
|
|
12
|
+
const hasSavedSession = typeof window !== 'undefined' &&
|
|
13
|
+
!!((window.sessionStorage.getItem('vanira_prospect_id') || window.localStorage.getItem('vanira_prospect_id')) &&
|
|
14
|
+
(window.sessionStorage.getItem('vanira_latest_call_id') || window.localStorage.getItem('vanira_latest_call_id')));
|
|
15
|
+
|
|
16
|
+
this.render(hasSavedSession);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private render(hasSavedSession: boolean) {
|
|
20
|
+
this.element.innerHTML = `
|
|
21
|
+
<div class="central-orb-container">
|
|
22
|
+
<div class="central-orb idle">
|
|
23
|
+
${icons.voice_orb}
|
|
24
|
+
</div>
|
|
25
|
+
<div class="central-orb-glow"></div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="voice-status-text">Tap to speak</div>
|
|
28
|
+
<div class="voice-status-subtext">Start a conversation</div>
|
|
29
|
+
|
|
30
|
+
<div class="session-selector-container" style="display: none; opacity: 0; transition: opacity 0.3s ease;">
|
|
31
|
+
<div class="session-option" data-behavior="continue">
|
|
32
|
+
<span class="session-title">Continue Discussion</span>
|
|
33
|
+
<span class="session-desc">Resume last session</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="session-option" data-behavior="new">
|
|
36
|
+
<span class="session-title">New Inquiry</span>
|
|
37
|
+
<span class="session-desc">Begin a new session</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const orbContainer = this.element.querySelector('.central-orb-container') as HTMLElement;
|
|
43
|
+
const selectorContainer = this.element.querySelector('.session-selector-container') as HTMLElement;
|
|
44
|
+
const statusText = this.element.querySelector('.voice-status-text') as HTMLElement;
|
|
45
|
+
const subtext = this.element.querySelector('.voice-status-subtext') as HTMLElement;
|
|
46
|
+
|
|
47
|
+
if (orbContainer) {
|
|
48
|
+
orbContainer.onclick = () => {
|
|
49
|
+
if (hasSavedSession && !this.showSessionSelector) {
|
|
50
|
+
this.showSessionSelector = true;
|
|
51
|
+
if (statusText) statusText.textContent = 'Select option to begin';
|
|
52
|
+
if (subtext) subtext.textContent = 'Choose inquiry type';
|
|
53
|
+
if (selectorContainer) {
|
|
54
|
+
selectorContainer.style.display = 'flex';
|
|
55
|
+
// Trigger reflow
|
|
56
|
+
selectorContainer.offsetHeight;
|
|
57
|
+
selectorContainer.style.opacity = '1';
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
this.onClick('continue');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Setup session behavior interactive selectors
|
|
66
|
+
const options = this.element.querySelectorAll('.session-option');
|
|
67
|
+
options.forEach(opt => {
|
|
68
|
+
opt.addEventListener('click', (e) => {
|
|
69
|
+
e.stopPropagation(); // Prevent launching call when clicking selection tabs
|
|
70
|
+
const behavior = opt.getAttribute('data-behavior') as 'continue' | 'new';
|
|
71
|
+
this.onClick(behavior);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public getElement(): HTMLDivElement {
|
|
77
|
+
return this.element;
|
|
78
|
+
}
|
|
79
|
+
}
|