@convai/web-sdk 0.1.1-beta.3 → 0.1.1-beta.5
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 +41 -2
- package/dist/core/AudioManager.d.ts +9 -1
- package/dist/core/AudioManager.d.ts.map +1 -1
- package/dist/core/AudioManager.js +44 -6
- package/dist/core/AudioManager.js.map +1 -1
- package/dist/core/ConvaiClient.d.ts +18 -9
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +75 -49
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/MessageHandler.d.ts.map +1 -1
- package/dist/core/MessageHandler.js +26 -24
- package/dist/core/MessageHandler.js.map +1 -1
- package/dist/react/components/ConvaiWidget.d.ts.map +1 -1
- package/dist/react/components/ConvaiWidget.js +5 -10
- package/dist/react/components/ConvaiWidget.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +12 -9
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/types/index.d.ts +1 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/vanilla/AudioRenderer.d.ts +45 -0
- package/dist/vanilla/AudioRenderer.d.ts.map +1 -0
- package/dist/vanilla/AudioRenderer.js +126 -0
- package/dist/vanilla/AudioRenderer.js.map +1 -0
- package/dist/vanilla/ConvaiWidget.d.ts +39 -0
- package/dist/vanilla/ConvaiWidget.d.ts.map +1 -0
- package/dist/vanilla/ConvaiWidget.js +1577 -0
- package/dist/vanilla/ConvaiWidget.js.map +1 -0
- package/dist/vanilla/icons.d.ts +34 -0
- package/dist/vanilla/icons.d.ts.map +1 -0
- package/dist/vanilla/icons.js +215 -0
- package/dist/vanilla/icons.js.map +1 -0
- package/dist/vanilla/index.d.ts +37 -0
- package/dist/vanilla/index.d.ts.map +1 -0
- package/dist/vanilla/index.js +39 -0
- package/dist/vanilla/index.js.map +1 -0
- package/dist/{components/rtc-widget/styles/theme.d.ts → vanilla/styles.d.ts} +14 -50
- package/dist/vanilla/styles.d.ts.map +1 -0
- package/dist/vanilla/styles.js +287 -0
- package/dist/vanilla/styles.js.map +1 -0
- package/dist/vanilla/types.d.ts +38 -0
- package/dist/vanilla/types.d.ts.map +1 -0
- package/dist/vanilla/types.js +2 -0
- package/dist/vanilla/types.js.map +1 -0
- package/package.json +12 -5
- package/dist/components/ConvaiWidget.d.ts +0 -59
- package/dist/components/ConvaiWidget.d.ts.map +0 -1
- package/dist/components/ConvaiWidget.js +0 -421
- package/dist/components/ConvaiWidget.js.map +0 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/index.js +0 -5
- package/dist/components/index.js.map +0 -1
- package/dist/components/rtc-widget/components/AudioSettingsPanel.d.ts +0 -10
- package/dist/components/rtc-widget/components/AudioSettingsPanel.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/AudioSettingsPanel.js +0 -316
- package/dist/components/rtc-widget/components/AudioSettingsPanel.js.map +0 -1
- package/dist/components/rtc-widget/components/ConviMessage.d.ts +0 -10
- package/dist/components/rtc-widget/components/ConviMessage.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/ConviMessage.js +0 -14
- package/dist/components/rtc-widget/components/ConviMessage.js.map +0 -1
- package/dist/components/rtc-widget/components/FloatingVideo.d.ts +0 -9
- package/dist/components/rtc-widget/components/FloatingVideo.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/FloatingVideo.js +0 -122
- package/dist/components/rtc-widget/components/FloatingVideo.js.map +0 -1
- package/dist/components/rtc-widget/components/MarkdownRenderer.d.ts +0 -7
- package/dist/components/rtc-widget/components/MarkdownRenderer.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/MarkdownRenderer.js +0 -68
- package/dist/components/rtc-widget/components/MarkdownRenderer.js.map +0 -1
- package/dist/components/rtc-widget/components/MessageBubble.d.ts +0 -10
- package/dist/components/rtc-widget/components/MessageBubble.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/MessageBubble.js +0 -23
- package/dist/components/rtc-widget/components/MessageBubble.js.map +0 -1
- package/dist/components/rtc-widget/components/MessageList.d.ts +0 -11
- package/dist/components/rtc-widget/components/MessageList.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/MessageList.js +0 -89
- package/dist/components/rtc-widget/components/MessageList.js.map +0 -1
- package/dist/components/rtc-widget/components/UserMessage.d.ts +0 -9
- package/dist/components/rtc-widget/components/UserMessage.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/UserMessage.js +0 -15
- package/dist/components/rtc-widget/components/UserMessage.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviButton.d.ts +0 -6
- package/dist/components/rtc-widget/components/conviComponents/ConviButton.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviButton.js +0 -15
- package/dist/components/rtc-widget/components/conviComponents/ConviButton.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviFooter.d.ts +0 -25
- package/dist/components/rtc-widget/components/conviComponents/ConviFooter.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviFooter.js +0 -172
- package/dist/components/rtc-widget/components/conviComponents/ConviFooter.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviHeader.d.ts +0 -17
- package/dist/components/rtc-widget/components/conviComponents/ConviHeader.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/ConviHeader.js +0 -66
- package/dist/components/rtc-widget/components/conviComponents/ConviHeader.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/SettingsTray.d.ts +0 -12
- package/dist/components/rtc-widget/components/conviComponents/SettingsTray.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/SettingsTray.js +0 -68
- package/dist/components/rtc-widget/components/conviComponents/SettingsTray.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts +0 -8
- package/dist/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js +0 -199
- package/dist/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/index.d.ts +0 -6
- package/dist/components/rtc-widget/components/conviComponents/index.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/conviComponents/index.js +0 -6
- package/dist/components/rtc-widget/components/conviComponents/index.js.map +0 -1
- package/dist/components/rtc-widget/components/index.d.ts +0 -8
- package/dist/components/rtc-widget/components/index.d.ts.map +0 -1
- package/dist/components/rtc-widget/components/index.js +0 -13
- package/dist/components/rtc-widget/components/index.js.map +0 -1
- package/dist/components/rtc-widget/index.d.ts +0 -6
- package/dist/components/rtc-widget/index.d.ts.map +0 -1
- package/dist/components/rtc-widget/index.js +0 -9
- package/dist/components/rtc-widget/index.js.map +0 -1
- package/dist/components/rtc-widget/styles/framerConfig.d.ts +0 -116
- package/dist/components/rtc-widget/styles/framerConfig.d.ts.map +0 -1
- package/dist/components/rtc-widget/styles/framerConfig.js +0 -73
- package/dist/components/rtc-widget/styles/framerConfig.js.map +0 -1
- package/dist/components/rtc-widget/styles/icons.d.ts +0 -28
- package/dist/components/rtc-widget/styles/icons.d.ts.map +0 -1
- package/dist/components/rtc-widget/styles/icons.js +0 -257
- package/dist/components/rtc-widget/styles/icons.js.map +0 -1
- package/dist/components/rtc-widget/styles/index.d.ts +0 -6
- package/dist/components/rtc-widget/styles/index.d.ts.map +0 -1
- package/dist/components/rtc-widget/styles/index.js +0 -9
- package/dist/components/rtc-widget/styles/index.js.map +0 -1
- package/dist/components/rtc-widget/styles/styledComponents.d.ts +0 -90
- package/dist/components/rtc-widget/styles/styledComponents.d.ts.map +0 -1
- package/dist/components/rtc-widget/styles/styledComponents.js +0 -661
- package/dist/components/rtc-widget/styles/styledComponents.js.map +0 -1
- package/dist/components/rtc-widget/styles/theme.d.ts.map +0 -1
- package/dist/components/rtc-widget/styles/theme.js +0 -290
- package/dist/components/rtc-widget/styles/theme.js.map +0 -1
- package/dist/components/rtc-widget/types/index.d.ts +0 -60
- package/dist/components/rtc-widget/types/index.d.ts.map +0 -1
- package/dist/components/rtc-widget/types/index.js +0 -2
- package/dist/components/rtc-widget/types/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -13
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -14
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/useAudioControls.d.ts +0 -41
- package/dist/hooks/useAudioControls.d.ts.map +0 -1
- package/dist/hooks/useAudioControls.js +0 -208
- package/dist/hooks/useAudioControls.js.map +0 -1
- package/dist/hooks/useCharacterInfo.d.ts +0 -17
- package/dist/hooks/useCharacterInfo.d.ts.map +0 -1
- package/dist/hooks/useCharacterInfo.js +0 -60
- package/dist/hooks/useCharacterInfo.js.map +0 -1
- package/dist/hooks/useConvaiClient.d.ts +0 -30
- package/dist/hooks/useConvaiClient.d.ts.map +0 -1
- package/dist/hooks/useConvaiClient.js +0 -349
- package/dist/hooks/useConvaiClient.js.map +0 -1
- package/dist/hooks/useDynamicInfoUpdater.d.ts +0 -33
- package/dist/hooks/useDynamicInfoUpdater.d.ts.map +0 -1
- package/dist/hooks/useDynamicInfoUpdater.js +0 -49
- package/dist/hooks/useDynamicInfoUpdater.js.map +0 -1
- package/dist/hooks/useLocalCameraTrack.d.ts +0 -22
- package/dist/hooks/useLocalCameraTrack.d.ts.map +0 -1
- package/dist/hooks/useLocalCameraTrack.js +0 -34
- package/dist/hooks/useLocalCameraTrack.js.map +0 -1
- package/dist/hooks/useMessageHandler.d.ts +0 -28
- package/dist/hooks/useMessageHandler.d.ts.map +0 -1
- package/dist/hooks/useMessageHandler.js +0 -267
- package/dist/hooks/useMessageHandler.js.map +0 -1
- package/dist/hooks/useScreenShare.d.ts +0 -45
- package/dist/hooks/useScreenShare.d.ts.map +0 -1
- package/dist/hooks/useScreenShare.js +0 -186
- package/dist/hooks/useScreenShare.js.map +0 -1
- package/dist/hooks/useTemplateKeysUpdater.d.ts +0 -35
- package/dist/hooks/useTemplateKeysUpdater.d.ts.map +0 -1
- package/dist/hooks/useTemplateKeysUpdater.js +0 -51
- package/dist/hooks/useTemplateKeysUpdater.js.map +0 -1
- package/dist/hooks/useTriggerMessageSender.d.ts +0 -28
- package/dist/hooks/useTriggerMessageSender.d.ts.map +0 -1
- package/dist/hooks/useTriggerMessageSender.js +0 -46
- package/dist/hooks/useTriggerMessageSender.js.map +0 -1
- package/dist/hooks/useTtsToggle.d.ts +0 -37
- package/dist/hooks/useTtsToggle.d.ts.map +0 -1
- package/dist/hooks/useTtsToggle.js +0 -63
- package/dist/hooks/useTtsToggle.js.map +0 -1
- package/dist/hooks/useUserTextMessageSender.d.ts +0 -28
- package/dist/hooks/useUserTextMessageSender.d.ts.map +0 -1
- package/dist/hooks/useUserTextMessageSender.js +0 -58
- package/dist/hooks/useUserTextMessageSender.js.map +0 -1
- package/dist/hooks/useVideoControls.d.ts +0 -39
- package/dist/hooks/useVideoControls.d.ts.map +0 -1
- package/dist/hooks/useVideoControls.js +0 -193
- package/dist/hooks/useVideoControls.js.map +0 -1
|
@@ -0,0 +1,1577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanilla ConvaiWidget - Complete UI widget for Convai conversations
|
|
3
|
+
* Ports the React ConvaiWidget to vanilla TypeScript with DOM manipulation
|
|
4
|
+
*/
|
|
5
|
+
import { AudioRenderer } from './AudioRenderer';
|
|
6
|
+
import { aeroTheme, injectGlobalStyles } from './styles';
|
|
7
|
+
import { Icons } from './icons';
|
|
8
|
+
/**
|
|
9
|
+
* Create a Convai chat widget in the specified container
|
|
10
|
+
*
|
|
11
|
+
* @param container - HTML element to attach the widget to
|
|
12
|
+
* @param options - Widget configuration options
|
|
13
|
+
* @returns VanillaWidget instance with destroy method
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { ConvaiClient, createConvaiWidget } from '@convai/web-sdk/vanilla';
|
|
18
|
+
*
|
|
19
|
+
* const client = new ConvaiClient();
|
|
20
|
+
* await client.connect({
|
|
21
|
+
* apiKey: 'your-api-key',
|
|
22
|
+
* characterId: 'your-character-id'
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const widget = createConvaiWidget(document.body, {
|
|
26
|
+
* convaiClient: client,
|
|
27
|
+
* showVideo: true,
|
|
28
|
+
* showScreenShare: true
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Later, cleanup
|
|
32
|
+
* widget.destroy();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function createConvaiWidget(container, options) {
|
|
36
|
+
const { convaiClient, showVideo = true, showScreenShare = true, } = options;
|
|
37
|
+
// Inject global styles
|
|
38
|
+
injectGlobalStyles();
|
|
39
|
+
// State
|
|
40
|
+
let isOpen = false;
|
|
41
|
+
let isSettingsOpen = false;
|
|
42
|
+
let isVoiceMode = false;
|
|
43
|
+
let isMuted = false;
|
|
44
|
+
let isVideoVisible = false;
|
|
45
|
+
let inputValue = '';
|
|
46
|
+
let characterName = 'Character';
|
|
47
|
+
let characterImage = '';
|
|
48
|
+
let audioRenderer = null;
|
|
49
|
+
// DOM elements (will be created below)
|
|
50
|
+
let rootElement;
|
|
51
|
+
let morphingContainer;
|
|
52
|
+
let buttonContent;
|
|
53
|
+
let chatContent;
|
|
54
|
+
let headerElement;
|
|
55
|
+
let contentElement;
|
|
56
|
+
let footerElement;
|
|
57
|
+
let inputElement;
|
|
58
|
+
let messageListElement;
|
|
59
|
+
let settingsTray;
|
|
60
|
+
let floatingVideo;
|
|
61
|
+
let voiceModeOverlay;
|
|
62
|
+
// Audio Analysis State
|
|
63
|
+
let audioContext = null;
|
|
64
|
+
let analyzer = null;
|
|
65
|
+
let dataArray = null;
|
|
66
|
+
let rafId = null;
|
|
67
|
+
let source = null;
|
|
68
|
+
// Fetch character info
|
|
69
|
+
const fetchCharacterInfo = async () => {
|
|
70
|
+
if (!convaiClient.apiKey || !convaiClient.characterId)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(`https://api.convai.com/character/get?charID=${convaiClient.characterId}`, {
|
|
74
|
+
headers: {
|
|
75
|
+
'CONVAI-API-KEY': convaiClient.apiKey
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (response.ok) {
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
characterName = data.character_name || 'Character';
|
|
81
|
+
characterImage = data.model_details?.modelLink || '';
|
|
82
|
+
updateHeader();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error('Failed to fetch character info:', error);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
// Create root structure
|
|
90
|
+
const createDOM = () => {
|
|
91
|
+
rootElement = document.createElement('div');
|
|
92
|
+
rootElement.className = 'convai-widget';
|
|
93
|
+
rootElement.style.cssText = `
|
|
94
|
+
position: fixed;
|
|
95
|
+
bottom: 1.5rem;
|
|
96
|
+
right: 1.5rem;
|
|
97
|
+
z-index: ${aeroTheme.zIndex.modal};
|
|
98
|
+
font-family: ${aeroTheme.typography.fontFamily.primary};
|
|
99
|
+
`;
|
|
100
|
+
// Morphing container
|
|
101
|
+
morphingContainer = document.createElement('div');
|
|
102
|
+
morphingContainer.style.cssText = `
|
|
103
|
+
position: relative;
|
|
104
|
+
width: 4rem;
|
|
105
|
+
height: 4rem;
|
|
106
|
+
background: ${aeroTheme.colors.glass.backdrop};
|
|
107
|
+
backdrop-filter: ${aeroTheme.glass.backdrop};
|
|
108
|
+
border: ${aeroTheme.glass.border};
|
|
109
|
+
border-radius: 50%;
|
|
110
|
+
box-shadow: ${aeroTheme.shadows.glass};
|
|
111
|
+
transition: all 0.3s ease-in-out;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
`;
|
|
117
|
+
// Button content (logo)
|
|
118
|
+
buttonContent = document.createElement('div');
|
|
119
|
+
buttonContent.style.cssText = `
|
|
120
|
+
position: absolute;
|
|
121
|
+
inset: 0;
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
transition: opacity 0.3s, transform 0.3s;
|
|
126
|
+
opacity: 1;
|
|
127
|
+
transform: scale(1);
|
|
128
|
+
`;
|
|
129
|
+
const convaiLogo = Icons.ConvaiLogo('xl', 'idle');
|
|
130
|
+
convaiLogo.style.color = aeroTheme.colors.convai.light;
|
|
131
|
+
buttonContent.appendChild(convaiLogo);
|
|
132
|
+
// Chat content
|
|
133
|
+
chatContent = document.createElement('div');
|
|
134
|
+
chatContent.style.cssText = `
|
|
135
|
+
position: absolute;
|
|
136
|
+
inset: 0;
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
opacity: 0;
|
|
140
|
+
transform: scale(0.8);
|
|
141
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
`;
|
|
144
|
+
// Voice Mode Overlay
|
|
145
|
+
voiceModeOverlay = createVoiceModeOverlay();
|
|
146
|
+
chatContent.appendChild(voiceModeOverlay);
|
|
147
|
+
// Header
|
|
148
|
+
headerElement = createHeader();
|
|
149
|
+
// Content area
|
|
150
|
+
contentElement = document.createElement('div');
|
|
151
|
+
contentElement.style.cssText = `
|
|
152
|
+
flex: 1;
|
|
153
|
+
overflow-y: auto;
|
|
154
|
+
padding: 1rem;
|
|
155
|
+
background: transparent;
|
|
156
|
+
`;
|
|
157
|
+
messageListElement = createMessageList();
|
|
158
|
+
contentElement.appendChild(messageListElement);
|
|
159
|
+
// Footer
|
|
160
|
+
footerElement = createFooter();
|
|
161
|
+
chatContent.appendChild(headerElement);
|
|
162
|
+
chatContent.appendChild(contentElement);
|
|
163
|
+
chatContent.appendChild(footerElement);
|
|
164
|
+
morphingContainer.appendChild(buttonContent);
|
|
165
|
+
morphingContainer.appendChild(chatContent);
|
|
166
|
+
rootElement.appendChild(morphingContainer);
|
|
167
|
+
// Settings tray
|
|
168
|
+
settingsTray = createSettingsTray();
|
|
169
|
+
rootElement.appendChild(settingsTray);
|
|
170
|
+
// Floating video
|
|
171
|
+
floatingVideo = createFloatingVideo();
|
|
172
|
+
container.appendChild(floatingVideo);
|
|
173
|
+
container.appendChild(rootElement);
|
|
174
|
+
// Event listeners
|
|
175
|
+
morphingContainer.addEventListener('click', handleToggle);
|
|
176
|
+
};
|
|
177
|
+
// Create Voice Mode Overlay
|
|
178
|
+
const createVoiceModeOverlay = () => {
|
|
179
|
+
const overlay = document.createElement('div');
|
|
180
|
+
overlay.style.cssText = `
|
|
181
|
+
position: absolute;
|
|
182
|
+
top: 50%;
|
|
183
|
+
left: 50%;
|
|
184
|
+
transform: translate(-50%, -50%);
|
|
185
|
+
text-align: center;
|
|
186
|
+
padding: 1rem;
|
|
187
|
+
z-index: 10;
|
|
188
|
+
pointer-events: auto;
|
|
189
|
+
display: none;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 1.5rem;
|
|
193
|
+
`;
|
|
194
|
+
// Bars Container
|
|
195
|
+
const barsContainer = document.createElement('div');
|
|
196
|
+
barsContainer.id = 'voice-bars-container';
|
|
197
|
+
barsContainer.style.cssText = `
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
gap: 2px;
|
|
202
|
+
height: 80px;
|
|
203
|
+
max-width: 300px;
|
|
204
|
+
`;
|
|
205
|
+
// Create 40 bars
|
|
206
|
+
for (let i = 0; i < 40; i++) {
|
|
207
|
+
const bar = document.createElement('div');
|
|
208
|
+
bar.className = 'voice-bar';
|
|
209
|
+
bar.style.cssText = `
|
|
210
|
+
width: 3px;
|
|
211
|
+
height: 15px;
|
|
212
|
+
background-color: ${aeroTheme.colors.neutral[400]};
|
|
213
|
+
border-radius: 1.5px;
|
|
214
|
+
transition: height 0.08s ease-out, background-color 0.2s;
|
|
215
|
+
transform-origin: center;
|
|
216
|
+
`;
|
|
217
|
+
barsContainer.appendChild(bar);
|
|
218
|
+
}
|
|
219
|
+
overlay.appendChild(barsContainer);
|
|
220
|
+
// Status Text
|
|
221
|
+
const statusContainer = document.createElement('div');
|
|
222
|
+
const statusTitle = document.createElement('div');
|
|
223
|
+
statusTitle.id = 'voice-mode-title';
|
|
224
|
+
statusTitle.style.cssText = `
|
|
225
|
+
font-size: 14px;
|
|
226
|
+
font-weight: 500;
|
|
227
|
+
color: ${aeroTheme.colors.text.primary};
|
|
228
|
+
margin-bottom: 0.5rem;
|
|
229
|
+
`;
|
|
230
|
+
statusTitle.textContent = 'Voice Only Mode';
|
|
231
|
+
const statusSubtitle = document.createElement('div');
|
|
232
|
+
statusSubtitle.id = 'voice-mode-subtitle';
|
|
233
|
+
statusSubtitle.style.cssText = `
|
|
234
|
+
font-size: 12px;
|
|
235
|
+
color: ${aeroTheme.colors.text.secondary};
|
|
236
|
+
`;
|
|
237
|
+
statusSubtitle.textContent = 'Press and hold the microphone to talk';
|
|
238
|
+
statusContainer.appendChild(statusTitle);
|
|
239
|
+
statusContainer.appendChild(statusSubtitle);
|
|
240
|
+
overlay.appendChild(statusContainer);
|
|
241
|
+
return overlay;
|
|
242
|
+
};
|
|
243
|
+
// Audio Analysis State for Voice Mode
|
|
244
|
+
let audioLevels = Array(40).fill(0);
|
|
245
|
+
let targetLevels = Array(40).fill(0.05);
|
|
246
|
+
let currentLevels = Array(40).fill(0.05);
|
|
247
|
+
let startTime = 0;
|
|
248
|
+
// Audio Analysis Logic
|
|
249
|
+
const updateAudioBars = () => {
|
|
250
|
+
if (!voiceModeOverlay)
|
|
251
|
+
return;
|
|
252
|
+
const bars = voiceModeOverlay.querySelectorAll('.voice-bar');
|
|
253
|
+
const isTalking = convaiClient.state.isSpeaking;
|
|
254
|
+
const isListening = !convaiClient.audioControls.isAudioMuted;
|
|
255
|
+
const isAnimating = isListening || isTalking;
|
|
256
|
+
// Update colors based on state
|
|
257
|
+
bars.forEach((bar) => {
|
|
258
|
+
bar.style.backgroundColor = isTalking
|
|
259
|
+
? aeroTheme.colors.convai.light
|
|
260
|
+
: isListening
|
|
261
|
+
? aeroTheme.colors.text.primary
|
|
262
|
+
: aeroTheme.colors.neutral[400];
|
|
263
|
+
});
|
|
264
|
+
// Update Text
|
|
265
|
+
const title = document.getElementById('voice-mode-title');
|
|
266
|
+
const subtitle = document.getElementById('voice-mode-subtitle');
|
|
267
|
+
if (title) {
|
|
268
|
+
title.textContent = isTalking
|
|
269
|
+
? "Character Speaking..."
|
|
270
|
+
: isListening
|
|
271
|
+
? "Listening..."
|
|
272
|
+
: "Voice Only Mode";
|
|
273
|
+
}
|
|
274
|
+
if (subtitle) {
|
|
275
|
+
subtitle.textContent = isListening || isTalking
|
|
276
|
+
? "Audio active"
|
|
277
|
+
: "Press and hold the microphone to talk";
|
|
278
|
+
}
|
|
279
|
+
// Animation Logic - Matches React version
|
|
280
|
+
if (isListening && analyzer && dataArray) {
|
|
281
|
+
// Use time domain data (waveform) instead of frequency
|
|
282
|
+
// @ts-ignore - TypeScript strict mode issue with Uint8Array type
|
|
283
|
+
analyzer.getByteTimeDomainData(dataArray);
|
|
284
|
+
// Calculate RMS (Root Mean Square) for volume
|
|
285
|
+
let sum = 0;
|
|
286
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
287
|
+
const normalized = (dataArray[i] - 128) / 128; // Center around 0
|
|
288
|
+
sum += normalized * normalized;
|
|
289
|
+
}
|
|
290
|
+
const rms = Math.sqrt(sum / dataArray.length);
|
|
291
|
+
// Apply some scaling and clamping
|
|
292
|
+
const volume = Math.min(1, rms * 3); // Boost sensitivity
|
|
293
|
+
// Create left-to-right wave effect
|
|
294
|
+
const minHeight = 8;
|
|
295
|
+
const maxHeight = 70;
|
|
296
|
+
bars.forEach((bar, i) => {
|
|
297
|
+
// Progressive wave from left to right
|
|
298
|
+
const position = i / 40; // 0 to 1 from left to right
|
|
299
|
+
const wavePhase = Date.now() / 300 + position * Math.PI * 2;
|
|
300
|
+
const waveVariation = Math.sin(wavePhase) * 0.15 + 0.85; // 0.7 to 1.0
|
|
301
|
+
const level = volume * waveVariation;
|
|
302
|
+
const height = minHeight + level * (maxHeight - minHeight);
|
|
303
|
+
bar.style.height = `${Math.max(minHeight, height)}px`;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else if (isTalking) {
|
|
307
|
+
// Simulate speaking bars with natural speech patterns
|
|
308
|
+
const elapsed = (Date.now() - startTime) / 1000; // seconds
|
|
309
|
+
// Generate new random target levels occasionally (simulating syllables/words)
|
|
310
|
+
if (Math.random() < 0.08) { // 8% chance per frame = ~5 times per second
|
|
311
|
+
targetLevels = Array(40).fill(0).map((_, i) => {
|
|
312
|
+
// More variation in the middle bars, less on edges for natural spread
|
|
313
|
+
const position = i / 40;
|
|
314
|
+
const centerWeight = 1 - Math.abs(position - 0.5) * 0.5;
|
|
315
|
+
// Random peaks and valleys like speech patterns
|
|
316
|
+
const randomPeak = 0.2 + Math.random() * 0.7; // 0.2 to 0.9
|
|
317
|
+
// Add some neighbor correlation so bars don't jump independently
|
|
318
|
+
const prevTarget = targetLevels[i] || 0.3;
|
|
319
|
+
const correlation = prevTarget * 0.4 + randomPeak * 0.6;
|
|
320
|
+
return correlation * centerWeight;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// Smoothly interpolate current levels toward targets (organic movement)
|
|
324
|
+
currentLevels = currentLevels.map((current, i) => {
|
|
325
|
+
const target = targetLevels[i];
|
|
326
|
+
const speed = 0.2; // Smooth but responsive
|
|
327
|
+
return current + (target - current) * speed;
|
|
328
|
+
});
|
|
329
|
+
// Apply a gentle fade-in for the first 0.3 seconds
|
|
330
|
+
const fadeIn = Math.min(1, elapsed / 0.3);
|
|
331
|
+
const minHeight = 8;
|
|
332
|
+
const maxHeight = 70;
|
|
333
|
+
bars.forEach((bar, i) => {
|
|
334
|
+
// Small random jitter for micro-variation
|
|
335
|
+
const microJitter = 0.95 + Math.random() * 0.1; // 0.95 to 1.05
|
|
336
|
+
const level = Math.max(0.05, Math.min(1, currentLevels[i] * fadeIn * microJitter));
|
|
337
|
+
const height = minHeight + level * (maxHeight - minHeight);
|
|
338
|
+
bar.style.height = `${Math.max(minHeight, height)}px`;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Reset to idle state
|
|
343
|
+
bars.forEach((bar) => {
|
|
344
|
+
bar.style.height = '15px';
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
rafId = requestAnimationFrame(updateAudioBars);
|
|
348
|
+
};
|
|
349
|
+
const startAudioAnalysis = async () => {
|
|
350
|
+
try {
|
|
351
|
+
if (!audioContext) {
|
|
352
|
+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
353
|
+
}
|
|
354
|
+
if (audioContext.state === 'suspended') {
|
|
355
|
+
await audioContext.resume();
|
|
356
|
+
}
|
|
357
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
358
|
+
analyzer = audioContext.createAnalyser();
|
|
359
|
+
analyzer.fftSize = 256;
|
|
360
|
+
analyzer.smoothingTimeConstant = 0.7;
|
|
361
|
+
source = audioContext.createMediaStreamSource(stream);
|
|
362
|
+
source.connect(analyzer);
|
|
363
|
+
dataArray = new Uint8Array(analyzer.fftSize);
|
|
364
|
+
updateAudioBars();
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
console.error("Audio analysis setup failed:", e);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const stopAudioAnalysis = () => {
|
|
371
|
+
if (rafId)
|
|
372
|
+
cancelAnimationFrame(rafId);
|
|
373
|
+
if (source) {
|
|
374
|
+
source.disconnect();
|
|
375
|
+
source.mediaStream.getTracks().forEach(t => t.stop());
|
|
376
|
+
source = null;
|
|
377
|
+
}
|
|
378
|
+
// Keep context alive if possible, or close it? React component closes it.
|
|
379
|
+
// We can keep it for reuse or close. Let's keep it simple.
|
|
380
|
+
};
|
|
381
|
+
// Create header
|
|
382
|
+
const createHeader = () => {
|
|
383
|
+
const header = document.createElement('div');
|
|
384
|
+
header.style.cssText = `
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: space-between;
|
|
388
|
+
padding: 1rem;
|
|
389
|
+
border-bottom: 1px solid ${aeroTheme.colors.neutral[200]};
|
|
390
|
+
background: white;
|
|
391
|
+
`;
|
|
392
|
+
const leftSection = document.createElement('div');
|
|
393
|
+
leftSection.style.cssText = `
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
gap: 0.5rem;
|
|
397
|
+
flex: 1;
|
|
398
|
+
`;
|
|
399
|
+
const closeButton = document.createElement('button');
|
|
400
|
+
const chevronIcon = Icons.ChevronDown('md');
|
|
401
|
+
closeButton.appendChild(chevronIcon);
|
|
402
|
+
closeButton.style.cssText = `
|
|
403
|
+
font-size: 1.25rem;
|
|
404
|
+
color: ${aeroTheme.colors.text.secondary};
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
padding: 0.25rem;
|
|
407
|
+
transition: ${aeroTheme.transitions.fast};
|
|
408
|
+
`;
|
|
409
|
+
closeButton.addEventListener('click', (e) => {
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
handleClose();
|
|
412
|
+
});
|
|
413
|
+
const titleSection = document.createElement('div');
|
|
414
|
+
titleSection.style.cssText = `
|
|
415
|
+
display: flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
gap: 0.5rem;
|
|
418
|
+
flex: 1;
|
|
419
|
+
font-size: ${aeroTheme.typography.fontSize.base};
|
|
420
|
+
font-weight: ${aeroTheme.typography.fontWeight.semibold};
|
|
421
|
+
color: ${aeroTheme.colors.text.primary};
|
|
422
|
+
`;
|
|
423
|
+
titleSection.id = 'convai-widget-title';
|
|
424
|
+
const settingsButton = document.createElement('button');
|
|
425
|
+
const moreIcon = Icons.MoreVertical('md');
|
|
426
|
+
settingsButton.appendChild(moreIcon);
|
|
427
|
+
settingsButton.style.cssText = `
|
|
428
|
+
font-size: 1.5rem;
|
|
429
|
+
color: ${aeroTheme.colors.text.secondary};
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
padding: 0.25rem;
|
|
432
|
+
transition: ${aeroTheme.transitions.fast};
|
|
433
|
+
`;
|
|
434
|
+
settingsButton.addEventListener('click', (e) => {
|
|
435
|
+
e.stopPropagation();
|
|
436
|
+
handleSettingsToggle();
|
|
437
|
+
});
|
|
438
|
+
leftSection.appendChild(closeButton);
|
|
439
|
+
leftSection.appendChild(titleSection);
|
|
440
|
+
header.appendChild(leftSection);
|
|
441
|
+
header.appendChild(settingsButton);
|
|
442
|
+
return header;
|
|
443
|
+
};
|
|
444
|
+
// Update header with character info
|
|
445
|
+
const updateHeader = () => {
|
|
446
|
+
const titleSection = document.getElementById('convai-widget-title');
|
|
447
|
+
if (!titleSection)
|
|
448
|
+
return;
|
|
449
|
+
titleSection.innerHTML = '';
|
|
450
|
+
if (characterImage) {
|
|
451
|
+
const img = document.createElement('img');
|
|
452
|
+
img.src = characterImage;
|
|
453
|
+
img.alt = characterName;
|
|
454
|
+
img.style.cssText = `
|
|
455
|
+
width: 1.5rem;
|
|
456
|
+
height: 1.5rem;
|
|
457
|
+
border-radius: 50%;
|
|
458
|
+
object-fit: cover;
|
|
459
|
+
border: 1.5px solid ${getBotStatusColor().color};
|
|
460
|
+
box-shadow: 0 0 6px ${getBotStatusColor().color}40;
|
|
461
|
+
transition: all 0.3s ease;
|
|
462
|
+
`;
|
|
463
|
+
titleSection.appendChild(img);
|
|
464
|
+
}
|
|
465
|
+
const nameSpan = document.createElement('span');
|
|
466
|
+
nameSpan.textContent = characterName;
|
|
467
|
+
titleSection.appendChild(nameSpan);
|
|
468
|
+
// Mute button
|
|
469
|
+
const muteButton = document.createElement('button');
|
|
470
|
+
muteButton.innerHTML = '';
|
|
471
|
+
const volumeIcon = isMuted ? Icons.VolumeMute('sm') : Icons.VolumeHigh('sm');
|
|
472
|
+
volumeIcon.style.color = isMuted ? '#919EABA6' : '#0E7360';
|
|
473
|
+
muteButton.appendChild(volumeIcon);
|
|
474
|
+
muteButton.style.cssText = `
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
background: transparent;
|
|
477
|
+
border: none;
|
|
478
|
+
box-shadow: none;
|
|
479
|
+
width: auto;
|
|
480
|
+
height: auto;
|
|
481
|
+
padding: 0;
|
|
482
|
+
display: inline-flex;
|
|
483
|
+
align-items: center;
|
|
484
|
+
margin-left: 0.5rem;
|
|
485
|
+
outline: none;
|
|
486
|
+
transition: transform 0.1s ease-out;
|
|
487
|
+
`;
|
|
488
|
+
muteButton.addEventListener('click', (e) => {
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
handleToggleMute();
|
|
491
|
+
});
|
|
492
|
+
muteButton.addEventListener('mouseenter', () => {
|
|
493
|
+
muteButton.style.transform = 'scale(1.1)';
|
|
494
|
+
});
|
|
495
|
+
muteButton.addEventListener('mouseleave', () => {
|
|
496
|
+
muteButton.style.transform = 'scale(1)';
|
|
497
|
+
});
|
|
498
|
+
titleSection.appendChild(muteButton);
|
|
499
|
+
// Voice Mode Badge
|
|
500
|
+
if (isVoiceMode) {
|
|
501
|
+
const voiceBadge = document.createElement('span');
|
|
502
|
+
voiceBadge.textContent = 'VOICE';
|
|
503
|
+
voiceBadge.style.cssText = `
|
|
504
|
+
font-size: 10px;
|
|
505
|
+
color: ${aeroTheme.colors.convai.light};
|
|
506
|
+
font-weight: 500;
|
|
507
|
+
margin-left: 8px;
|
|
508
|
+
padding: 2px 6px;
|
|
509
|
+
border-radius: 4px;
|
|
510
|
+
background-color: ${aeroTheme.colors.convai.light}20;
|
|
511
|
+
`;
|
|
512
|
+
titleSection.appendChild(voiceBadge);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
// Create message list
|
|
516
|
+
const createMessageList = () => {
|
|
517
|
+
const list = document.createElement('div');
|
|
518
|
+
list.id = 'convai-message-list';
|
|
519
|
+
list.style.cssText = `
|
|
520
|
+
display: flex;
|
|
521
|
+
flex-direction: column;
|
|
522
|
+
gap: 0.75rem;
|
|
523
|
+
min-height: 100%;
|
|
524
|
+
`;
|
|
525
|
+
return list;
|
|
526
|
+
};
|
|
527
|
+
// Create footer
|
|
528
|
+
const createFooter = () => {
|
|
529
|
+
const footer = document.createElement('div');
|
|
530
|
+
footer.style.cssText = `
|
|
531
|
+
padding: 1rem;
|
|
532
|
+
border-top: 1px solid ${aeroTheme.colors.neutral[200]};
|
|
533
|
+
background: white;
|
|
534
|
+
display: flex;
|
|
535
|
+
gap: 0.5rem;
|
|
536
|
+
align-items: center;
|
|
537
|
+
position: relative;
|
|
538
|
+
`;
|
|
539
|
+
// Voice Mode Exit Button (Initially hidden)
|
|
540
|
+
const voiceExitButton = document.createElement('button');
|
|
541
|
+
voiceExitButton.id = 'convai-voice-exit-btn';
|
|
542
|
+
const exitIcon = Icons.Waveform('md');
|
|
543
|
+
voiceExitButton.appendChild(exitIcon);
|
|
544
|
+
voiceExitButton.style.cssText = `
|
|
545
|
+
width: 2.25rem;
|
|
546
|
+
height: 2.25rem;
|
|
547
|
+
border-radius: 50%;
|
|
548
|
+
background: ${aeroTheme.colors.error[500]};
|
|
549
|
+
color: white;
|
|
550
|
+
display: none; /* Hidden by default */
|
|
551
|
+
align-items: center;
|
|
552
|
+
justify-content: center;
|
|
553
|
+
cursor: pointer;
|
|
554
|
+
margin: 0 auto;
|
|
555
|
+
border: none;
|
|
556
|
+
`;
|
|
557
|
+
voiceExitButton.addEventListener('click', () => {
|
|
558
|
+
isVoiceMode = false;
|
|
559
|
+
updateVoiceMode();
|
|
560
|
+
});
|
|
561
|
+
footer.appendChild(voiceExitButton);
|
|
562
|
+
// Standard Footer Content (Mic + Input)
|
|
563
|
+
const standardContent = document.createElement('div');
|
|
564
|
+
standardContent.id = 'convai-footer-standard';
|
|
565
|
+
standardContent.style.cssText = `
|
|
566
|
+
display: flex;
|
|
567
|
+
gap: 0.5rem;
|
|
568
|
+
align-items: center;
|
|
569
|
+
width: 100%;
|
|
570
|
+
`;
|
|
571
|
+
// Mic button
|
|
572
|
+
const micButton = document.createElement('button');
|
|
573
|
+
micButton.id = 'convai-mic-button';
|
|
574
|
+
const micIcon = Icons.Mic('md');
|
|
575
|
+
micButton.appendChild(micIcon);
|
|
576
|
+
const initialMicBackground = convaiClient.audioControls.isAudioMuted
|
|
577
|
+
? aeroTheme.colors.error[500]
|
|
578
|
+
: aeroTheme.colors.text.primary;
|
|
579
|
+
micButton.style.cssText = `
|
|
580
|
+
width: 2.25rem;
|
|
581
|
+
height: 2.25rem;
|
|
582
|
+
border-radius: 50%;
|
|
583
|
+
background: ${initialMicBackground};
|
|
584
|
+
color: white;
|
|
585
|
+
display: flex;
|
|
586
|
+
align-items: center;
|
|
587
|
+
justify-content: center;
|
|
588
|
+
cursor: pointer;
|
|
589
|
+
transition: ${aeroTheme.transitions.fast};
|
|
590
|
+
flex-shrink: 0;
|
|
591
|
+
border: none;
|
|
592
|
+
`;
|
|
593
|
+
micButton.addEventListener('click', handleMicToggle);
|
|
594
|
+
standardContent.appendChild(micButton);
|
|
595
|
+
// Input container
|
|
596
|
+
const inputContainer = document.createElement('div');
|
|
597
|
+
inputContainer.style.cssText = `
|
|
598
|
+
flex: 1;
|
|
599
|
+
position: relative;
|
|
600
|
+
display: flex;
|
|
601
|
+
align-items: center;
|
|
602
|
+
`;
|
|
603
|
+
inputElement = document.createElement('input');
|
|
604
|
+
inputElement.type = 'text';
|
|
605
|
+
inputElement.placeholder = 'Conversation';
|
|
606
|
+
inputElement.style.cssText = `
|
|
607
|
+
width: 100%;
|
|
608
|
+
padding: 0.75rem 3rem 0.75rem 1rem;
|
|
609
|
+
border-radius: ${aeroTheme.borderRadius.full};
|
|
610
|
+
border: 1px solid ${aeroTheme.colors.neutral[300]};
|
|
611
|
+
background: ${aeroTheme.colors.glass.medium};
|
|
612
|
+
color: ${aeroTheme.colors.text.primary};
|
|
613
|
+
font-size: ${aeroTheme.typography.fontSize.sm};
|
|
614
|
+
transition: ${aeroTheme.transitions.fast};
|
|
615
|
+
outline: none;
|
|
616
|
+
`;
|
|
617
|
+
inputElement.addEventListener('input', (e) => {
|
|
618
|
+
inputValue = e.target.value;
|
|
619
|
+
updateSendButton();
|
|
620
|
+
});
|
|
621
|
+
inputElement.addEventListener('keypress', (e) => {
|
|
622
|
+
if (e.key === 'Enter')
|
|
623
|
+
handleSend();
|
|
624
|
+
});
|
|
625
|
+
// Send button
|
|
626
|
+
const sendButton = document.createElement('button');
|
|
627
|
+
sendButton.id = 'convai-send-button';
|
|
628
|
+
const sendIcon = Icons.Send('md');
|
|
629
|
+
sendButton.appendChild(sendIcon);
|
|
630
|
+
sendButton.style.cssText = `
|
|
631
|
+
position: absolute;
|
|
632
|
+
right: 0.375rem;
|
|
633
|
+
width: 2.25rem;
|
|
634
|
+
height: 2.25rem;
|
|
635
|
+
border-radius: 50%;
|
|
636
|
+
background: transparent; /* Initial transparent for voice toggle */
|
|
637
|
+
color: ${aeroTheme.colors.text.primary};
|
|
638
|
+
display: flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
justify-content: center;
|
|
641
|
+
cursor: pointer;
|
|
642
|
+
transition: ${aeroTheme.transitions.fast};
|
|
643
|
+
border: none;
|
|
644
|
+
`;
|
|
645
|
+
sendButton.addEventListener('click', () => {
|
|
646
|
+
if (inputValue.length > 0) {
|
|
647
|
+
handleSend();
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
// Toggle Voice Mode
|
|
651
|
+
isVoiceMode = true;
|
|
652
|
+
updateVoiceMode();
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
inputContainer.appendChild(inputElement);
|
|
656
|
+
inputContainer.appendChild(sendButton);
|
|
657
|
+
standardContent.appendChild(inputContainer);
|
|
658
|
+
footer.appendChild(standardContent);
|
|
659
|
+
return footer;
|
|
660
|
+
};
|
|
661
|
+
// Create settings tray - Matches React SettingsTray component exactly
|
|
662
|
+
const createSettingsTray = () => {
|
|
663
|
+
const tray = document.createElement('div');
|
|
664
|
+
tray.setAttribute('data-settings-tray', 'true');
|
|
665
|
+
tray.style.cssText = `
|
|
666
|
+
position: absolute;
|
|
667
|
+
top: 60px;
|
|
668
|
+
right: 16px;
|
|
669
|
+
background: white;
|
|
670
|
+
border-radius: ${aeroTheme.borderRadius.xl};
|
|
671
|
+
box-shadow: ${aeroTheme.shadows.xl};
|
|
672
|
+
padding: 0;
|
|
673
|
+
display: none;
|
|
674
|
+
flex-direction: row;
|
|
675
|
+
align-items: center;
|
|
676
|
+
gap: 0;
|
|
677
|
+
z-index: 9999;
|
|
678
|
+
min-width: auto;
|
|
679
|
+
transform-origin: top right;
|
|
680
|
+
animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
681
|
+
overflow: hidden;
|
|
682
|
+
`;
|
|
683
|
+
// Add animation keyframes if not present
|
|
684
|
+
if (!document.getElementById('convai-widget-keyframes')) {
|
|
685
|
+
const style = document.createElement('style');
|
|
686
|
+
style.id = 'convai-widget-keyframes';
|
|
687
|
+
style.textContent = `
|
|
688
|
+
@keyframes popIn {
|
|
689
|
+
from { opacity: 0; transform: scale(0.95) translateY(-10px); }
|
|
690
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
691
|
+
}
|
|
692
|
+
`;
|
|
693
|
+
document.head.appendChild(style);
|
|
694
|
+
}
|
|
695
|
+
// Horizontal Settings Row Container
|
|
696
|
+
const settingsRow = document.createElement('div');
|
|
697
|
+
settingsRow.style.cssText = `
|
|
698
|
+
display: flex;
|
|
699
|
+
flex-direction: row;
|
|
700
|
+
align-items: center;
|
|
701
|
+
padding: 8px;
|
|
702
|
+
gap: 4px;
|
|
703
|
+
`;
|
|
704
|
+
const createOption = (icon, label, onClick, isActive = false, isDestructive = false) => {
|
|
705
|
+
const btn = document.createElement('div');
|
|
706
|
+
btn.style.cssText = `
|
|
707
|
+
display: flex;
|
|
708
|
+
flex-direction: column;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: 4px;
|
|
711
|
+
padding: 8px 12px;
|
|
712
|
+
cursor: pointer;
|
|
713
|
+
border-radius: ${aeroTheme.borderRadius.md};
|
|
714
|
+
background-color: ${isActive
|
|
715
|
+
? 'rgba(16, 185, 129, 0.15)'
|
|
716
|
+
: isDestructive
|
|
717
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
718
|
+
: 'transparent'};
|
|
719
|
+
color: ${isActive
|
|
720
|
+
? '#10b981'
|
|
721
|
+
: isDestructive
|
|
722
|
+
? '#ef4444'
|
|
723
|
+
: aeroTheme.colors.text.primary};
|
|
724
|
+
transition: ${aeroTheme.transitions.fast};
|
|
725
|
+
min-width: 50px;
|
|
726
|
+
`;
|
|
727
|
+
// Icon container
|
|
728
|
+
const iconContainer = document.createElement('div');
|
|
729
|
+
iconContainer.appendChild(icon);
|
|
730
|
+
btn.appendChild(iconContainer);
|
|
731
|
+
const span = document.createElement('span');
|
|
732
|
+
span.textContent = label;
|
|
733
|
+
span.style.cssText = `
|
|
734
|
+
font-size: 10px;
|
|
735
|
+
font-weight: 500;
|
|
736
|
+
`;
|
|
737
|
+
btn.appendChild(span);
|
|
738
|
+
btn.addEventListener('click', (e) => {
|
|
739
|
+
e.stopPropagation();
|
|
740
|
+
onClick();
|
|
741
|
+
});
|
|
742
|
+
btn.addEventListener('mouseover', () => {
|
|
743
|
+
btn.style.transform = 'scale(1.02)';
|
|
744
|
+
if (!isActive && !isDestructive) {
|
|
745
|
+
btn.style.backgroundColor = aeroTheme.colors.neutral[100];
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
btn.addEventListener('mouseout', () => {
|
|
749
|
+
btn.style.transform = 'scale(1)';
|
|
750
|
+
btn.style.backgroundColor = isActive
|
|
751
|
+
? 'rgba(16, 185, 129, 0.15)'
|
|
752
|
+
: isDestructive
|
|
753
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
754
|
+
: 'transparent';
|
|
755
|
+
});
|
|
756
|
+
return btn;
|
|
757
|
+
};
|
|
758
|
+
// Reset
|
|
759
|
+
const resetIcon = Icons.Redo('md');
|
|
760
|
+
resetIcon.style.width = '18px';
|
|
761
|
+
resetIcon.style.height = '18px';
|
|
762
|
+
settingsRow.appendChild(createOption(resetIcon, 'Reset', handleReset));
|
|
763
|
+
// Video
|
|
764
|
+
if (convaiClient.connectionType === 'video' && showVideo) {
|
|
765
|
+
const videoIcon = isVideoVisible ? Icons.Video('md') : Icons.VideoOff('md');
|
|
766
|
+
videoIcon.style.width = '18px';
|
|
767
|
+
videoIcon.style.height = '18px';
|
|
768
|
+
const videoBtn = createOption(videoIcon, 'Video', handleToggleVideo, isVideoVisible);
|
|
769
|
+
videoBtn.id = 'convai-settings-video-btn';
|
|
770
|
+
settingsRow.appendChild(videoBtn);
|
|
771
|
+
}
|
|
772
|
+
// Screen Share
|
|
773
|
+
if (convaiClient.connectionType === 'video' && showScreenShare) {
|
|
774
|
+
const isSharing = convaiClient.screenShareControls.isScreenShareActive;
|
|
775
|
+
const shareIcon = isSharing ? Icons.StopScreenShare('md') : Icons.ScreenShare('md');
|
|
776
|
+
shareIcon.style.width = '18px';
|
|
777
|
+
shareIcon.style.height = '18px';
|
|
778
|
+
const shareBtn = createOption(shareIcon, 'Screen', handleToggleScreenShare, isSharing);
|
|
779
|
+
shareBtn.id = 'convai-settings-share-btn';
|
|
780
|
+
settingsRow.appendChild(shareBtn);
|
|
781
|
+
}
|
|
782
|
+
// Disconnect
|
|
783
|
+
const disconnectIcon = document.createElement('span');
|
|
784
|
+
disconnectIcon.innerHTML = '⏻';
|
|
785
|
+
disconnectIcon.style.fontSize = '18px';
|
|
786
|
+
settingsRow.appendChild(createOption(disconnectIcon, 'Disconnect', handleDisconnect, false, true));
|
|
787
|
+
tray.appendChild(settingsRow);
|
|
788
|
+
return tray;
|
|
789
|
+
};
|
|
790
|
+
// Create floating video - Matches React FloatingVideo component
|
|
791
|
+
const createFloatingVideo = () => {
|
|
792
|
+
const container = document.createElement('div');
|
|
793
|
+
container.id = 'floating-video-container';
|
|
794
|
+
container.style.cssText = `
|
|
795
|
+
position: fixed;
|
|
796
|
+
left: 20px;
|
|
797
|
+
top: 20px;
|
|
798
|
+
z-index: 10000;
|
|
799
|
+
width: 320px;
|
|
800
|
+
border-radius: ${aeroTheme.borderRadius.lg};
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
803
|
+
cursor: grab;
|
|
804
|
+
display: none;
|
|
805
|
+
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
806
|
+
`;
|
|
807
|
+
const wrapper = document.createElement('div');
|
|
808
|
+
wrapper.style.cssText = `
|
|
809
|
+
background: rgba(15, 23, 42, 0.95);
|
|
810
|
+
backdrop-filter: blur(20px);
|
|
811
|
+
border: 1px solid ${aeroTheme.colors.neutral[700]};
|
|
812
|
+
border-radius: ${aeroTheme.borderRadius.lg};
|
|
813
|
+
overflow: hidden;
|
|
814
|
+
`;
|
|
815
|
+
// Header
|
|
816
|
+
const header = document.createElement('div');
|
|
817
|
+
header.style.cssText = `
|
|
818
|
+
display: flex;
|
|
819
|
+
align-items: center;
|
|
820
|
+
justify-content: space-between;
|
|
821
|
+
padding: 8px 12px;
|
|
822
|
+
background: rgba(0, 0, 0, 0.3);
|
|
823
|
+
border-bottom: 1px solid ${aeroTheme.colors.neutral[700]};
|
|
824
|
+
`;
|
|
825
|
+
const headerLeft = document.createElement('div');
|
|
826
|
+
headerLeft.style.cssText = `
|
|
827
|
+
display: flex;
|
|
828
|
+
align-items: center;
|
|
829
|
+
gap: 8px;
|
|
830
|
+
color: ${aeroTheme.colors.neutral[300]};
|
|
831
|
+
font-size: 12px;
|
|
832
|
+
font-weight: 500;
|
|
833
|
+
`;
|
|
834
|
+
const dragIcon = Icons.DragIndicator('sm');
|
|
835
|
+
dragIcon.style.width = '16px';
|
|
836
|
+
dragIcon.style.height = '16px';
|
|
837
|
+
headerLeft.appendChild(dragIcon);
|
|
838
|
+
const label = document.createElement('span');
|
|
839
|
+
label.textContent = 'Your Camera';
|
|
840
|
+
headerLeft.appendChild(label);
|
|
841
|
+
const closeButton = document.createElement('button');
|
|
842
|
+
closeButton.innerHTML = '';
|
|
843
|
+
const closeIcon = Icons.Close('md');
|
|
844
|
+
closeIcon.style.width = '20px';
|
|
845
|
+
closeIcon.style.height = '20px';
|
|
846
|
+
closeButton.appendChild(closeIcon);
|
|
847
|
+
closeButton.style.cssText = `
|
|
848
|
+
background: transparent;
|
|
849
|
+
border: none;
|
|
850
|
+
color: ${aeroTheme.colors.neutral[400]};
|
|
851
|
+
cursor: pointer;
|
|
852
|
+
display: flex;
|
|
853
|
+
align-items: center;
|
|
854
|
+
justify-content: center;
|
|
855
|
+
padding: 4px;
|
|
856
|
+
transition: transform 0.1s ease-out;
|
|
857
|
+
`;
|
|
858
|
+
closeButton.addEventListener('click', (e) => {
|
|
859
|
+
e.stopPropagation();
|
|
860
|
+
convaiClient.videoControls.disableVideo();
|
|
861
|
+
});
|
|
862
|
+
closeButton.addEventListener('mouseenter', () => {
|
|
863
|
+
closeButton.style.transform = 'scale(1.1)';
|
|
864
|
+
});
|
|
865
|
+
closeButton.addEventListener('mouseleave', () => {
|
|
866
|
+
closeButton.style.transform = 'scale(1)';
|
|
867
|
+
});
|
|
868
|
+
header.appendChild(headerLeft);
|
|
869
|
+
header.appendChild(closeButton);
|
|
870
|
+
// Video Container
|
|
871
|
+
const videoContainer = document.createElement('div');
|
|
872
|
+
videoContainer.style.cssText = `
|
|
873
|
+
position: relative;
|
|
874
|
+
width: 100%;
|
|
875
|
+
padding-bottom: 75%;
|
|
876
|
+
background: #000;
|
|
877
|
+
`;
|
|
878
|
+
const videoWrapper = document.createElement('div');
|
|
879
|
+
videoWrapper.id = 'floating-video-wrapper';
|
|
880
|
+
videoWrapper.style.cssText = `
|
|
881
|
+
position: absolute;
|
|
882
|
+
top: 0;
|
|
883
|
+
left: 0;
|
|
884
|
+
width: 100%;
|
|
885
|
+
height: 100%;
|
|
886
|
+
`;
|
|
887
|
+
const videoElement = document.createElement('video');
|
|
888
|
+
videoElement.id = 'floating-video-element';
|
|
889
|
+
videoElement.autoplay = true;
|
|
890
|
+
videoElement.playsInline = true;
|
|
891
|
+
videoElement.muted = true;
|
|
892
|
+
videoElement.style.cssText = `
|
|
893
|
+
width: 100%;
|
|
894
|
+
height: 100%;
|
|
895
|
+
object-fit: cover;
|
|
896
|
+
transform: scaleX(-1);
|
|
897
|
+
`;
|
|
898
|
+
// Camera off placeholder
|
|
899
|
+
const cameraOffPlaceholder = document.createElement('div');
|
|
900
|
+
cameraOffPlaceholder.id = 'camera-off-placeholder';
|
|
901
|
+
cameraOffPlaceholder.style.cssText = `
|
|
902
|
+
width: 100%;
|
|
903
|
+
height: 100%;
|
|
904
|
+
display: flex;
|
|
905
|
+
flex-direction: column;
|
|
906
|
+
align-items: center;
|
|
907
|
+
justify-content: center;
|
|
908
|
+
color: ${aeroTheme.colors.neutral[400]};
|
|
909
|
+
gap: 12px;
|
|
910
|
+
background: #000;
|
|
911
|
+
`;
|
|
912
|
+
const cameraOffIcon = Icons.VideoOff('lg');
|
|
913
|
+
cameraOffIcon.style.width = '48px';
|
|
914
|
+
cameraOffIcon.style.height = '48px';
|
|
915
|
+
cameraOffPlaceholder.appendChild(cameraOffIcon);
|
|
916
|
+
const cameraOffText = document.createElement('span');
|
|
917
|
+
cameraOffText.textContent = 'Camera is off';
|
|
918
|
+
cameraOffText.style.cssText = `
|
|
919
|
+
font-size: 14px;
|
|
920
|
+
text-align: center;
|
|
921
|
+
`;
|
|
922
|
+
cameraOffPlaceholder.appendChild(cameraOffText);
|
|
923
|
+
videoWrapper.appendChild(videoElement);
|
|
924
|
+
videoWrapper.appendChild(cameraOffPlaceholder);
|
|
925
|
+
videoContainer.appendChild(videoWrapper);
|
|
926
|
+
wrapper.appendChild(header);
|
|
927
|
+
wrapper.appendChild(videoContainer);
|
|
928
|
+
container.appendChild(wrapper);
|
|
929
|
+
// Add drag functionality
|
|
930
|
+
let isDragging = false;
|
|
931
|
+
let startX = 0;
|
|
932
|
+
let startY = 0;
|
|
933
|
+
let startLeft = 20;
|
|
934
|
+
let startTop = 20;
|
|
935
|
+
const handleMouseDown = (e) => {
|
|
936
|
+
if (e.target.closest('button'))
|
|
937
|
+
return;
|
|
938
|
+
isDragging = true;
|
|
939
|
+
startX = e.clientX;
|
|
940
|
+
startY = e.clientY;
|
|
941
|
+
const style = window.getComputedStyle(container);
|
|
942
|
+
startLeft = parseInt(style.left);
|
|
943
|
+
startTop = parseInt(style.top);
|
|
944
|
+
container.style.cursor = 'grabbing';
|
|
945
|
+
e.preventDefault();
|
|
946
|
+
};
|
|
947
|
+
const handleMouseMove = (e) => {
|
|
948
|
+
if (!isDragging)
|
|
949
|
+
return;
|
|
950
|
+
const deltaX = e.clientX - startX;
|
|
951
|
+
const deltaY = e.clientY - startY;
|
|
952
|
+
const newLeft = startLeft + deltaX;
|
|
953
|
+
const newTop = startTop + deltaY;
|
|
954
|
+
// Keep within bounds
|
|
955
|
+
const maxX = window.innerWidth - 320;
|
|
956
|
+
const maxY = window.innerHeight - 240;
|
|
957
|
+
const boundedLeft = Math.max(0, Math.min(newLeft, maxX));
|
|
958
|
+
const boundedTop = Math.max(0, Math.min(newTop, maxY));
|
|
959
|
+
container.style.left = `${boundedLeft}px`;
|
|
960
|
+
container.style.top = `${boundedTop}px`;
|
|
961
|
+
};
|
|
962
|
+
const handleMouseUp = () => {
|
|
963
|
+
if (isDragging) {
|
|
964
|
+
isDragging = false;
|
|
965
|
+
container.style.cursor = 'grab';
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
container.addEventListener('mousedown', handleMouseDown);
|
|
969
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
970
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
971
|
+
return container;
|
|
972
|
+
};
|
|
973
|
+
// Event handlers
|
|
974
|
+
const handleToggle = async () => {
|
|
975
|
+
if (isOpen) {
|
|
976
|
+
// Just close if already open
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
// Connect on first click if not already connected/connecting
|
|
980
|
+
if (!convaiClient.state.isConnected && !convaiClient.state.isConnecting) {
|
|
981
|
+
try {
|
|
982
|
+
await convaiClient.connect();
|
|
983
|
+
setIsOpen(true);
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
console.error('Failed to connect:', error);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
// Just toggle open/close if already connected
|
|
991
|
+
setIsOpen(!isOpen);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
const handleClose = () => {
|
|
995
|
+
setIsOpen(false);
|
|
996
|
+
};
|
|
997
|
+
const handleSend = () => {
|
|
998
|
+
if (inputValue.trim() && convaiClient.state.isConnected) {
|
|
999
|
+
convaiClient.sendUserTextMessage(inputValue);
|
|
1000
|
+
inputValue = '';
|
|
1001
|
+
inputElement.value = '';
|
|
1002
|
+
updateSendButton();
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
const handleMicToggle = async () => {
|
|
1006
|
+
await convaiClient.audioControls.toggleAudio();
|
|
1007
|
+
updateMicButton();
|
|
1008
|
+
};
|
|
1009
|
+
const handleToggleMute = () => {
|
|
1010
|
+
isMuted = !isMuted;
|
|
1011
|
+
if (convaiClient.room) {
|
|
1012
|
+
const remoteParticipants = Array.from(convaiClient.room.remoteParticipants.values());
|
|
1013
|
+
remoteParticipants.forEach((participant) => {
|
|
1014
|
+
participant.audioTrackPublications.forEach((publication) => {
|
|
1015
|
+
if (publication.track) {
|
|
1016
|
+
publication.track.setMuted(isMuted);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
updateHeader();
|
|
1022
|
+
};
|
|
1023
|
+
const handleReset = async () => {
|
|
1024
|
+
setIsSettingsOpen(false);
|
|
1025
|
+
try {
|
|
1026
|
+
await convaiClient.disconnect();
|
|
1027
|
+
convaiClient.resetSession();
|
|
1028
|
+
isMuted = false;
|
|
1029
|
+
isVoiceMode = false;
|
|
1030
|
+
updateHeader();
|
|
1031
|
+
updateVoiceMode();
|
|
1032
|
+
updateMicButton();
|
|
1033
|
+
updateSendButton();
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
console.error('Failed to reset:', error);
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
const handleDisconnect = async () => {
|
|
1040
|
+
setIsSettingsOpen(false);
|
|
1041
|
+
await convaiClient.disconnect();
|
|
1042
|
+
isMuted = false;
|
|
1043
|
+
isVoiceMode = false;
|
|
1044
|
+
updateHeader();
|
|
1045
|
+
updateVoiceMode();
|
|
1046
|
+
updateMicButton();
|
|
1047
|
+
updateSendButton();
|
|
1048
|
+
};
|
|
1049
|
+
const handleToggleVideo = async () => {
|
|
1050
|
+
if (convaiClient.connectionType !== 'video')
|
|
1051
|
+
return;
|
|
1052
|
+
try {
|
|
1053
|
+
if (convaiClient.videoControls.isVideoEnabled) {
|
|
1054
|
+
await convaiClient.videoControls.disableVideo();
|
|
1055
|
+
}
|
|
1056
|
+
else {
|
|
1057
|
+
await convaiClient.videoControls.enableVideo();
|
|
1058
|
+
}
|
|
1059
|
+
isVideoVisible = convaiClient.videoControls.isVideoEnabled;
|
|
1060
|
+
updateVoiceMode(); // Updates UI based on video state
|
|
1061
|
+
// Update tray button state
|
|
1062
|
+
const videoBtn = document.getElementById('convai-settings-video-btn');
|
|
1063
|
+
if (videoBtn) {
|
|
1064
|
+
const isActive = isVideoVisible;
|
|
1065
|
+
videoBtn.style.backgroundColor = isActive ? 'rgba(16, 185, 129, 0.15)' : 'transparent';
|
|
1066
|
+
videoBtn.style.color = isActive ? '#10b981' : aeroTheme.colors.text.primary;
|
|
1067
|
+
// Update Icon - first child is icon container
|
|
1068
|
+
const iconContainer = videoBtn.querySelector('div');
|
|
1069
|
+
if (iconContainer) {
|
|
1070
|
+
iconContainer.innerHTML = '';
|
|
1071
|
+
const icon = isActive ? Icons.Video('md') : Icons.VideoOff('md');
|
|
1072
|
+
icon.style.width = '18px';
|
|
1073
|
+
icon.style.height = '18px';
|
|
1074
|
+
iconContainer.appendChild(icon);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
catch (e) {
|
|
1079
|
+
console.error("Toggle video failed", e);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
const handleToggleScreenShare = async () => {
|
|
1083
|
+
try {
|
|
1084
|
+
await convaiClient.screenShareControls.toggleScreenShare();
|
|
1085
|
+
const isSharing = convaiClient.screenShareControls.isScreenShareActive;
|
|
1086
|
+
const shareBtn = document.getElementById('convai-settings-share-btn');
|
|
1087
|
+
if (shareBtn) {
|
|
1088
|
+
const isActive = isSharing;
|
|
1089
|
+
shareBtn.style.backgroundColor = isActive ? 'rgba(16, 185, 129, 0.15)' : 'transparent';
|
|
1090
|
+
shareBtn.style.color = isActive ? '#10b981' : aeroTheme.colors.text.primary;
|
|
1091
|
+
// Update Icon - first child is icon container
|
|
1092
|
+
const iconContainer = shareBtn.querySelector('div');
|
|
1093
|
+
if (iconContainer) {
|
|
1094
|
+
iconContainer.innerHTML = '';
|
|
1095
|
+
const icon = isActive ? Icons.StopScreenShare('md') : Icons.ScreenShare('md');
|
|
1096
|
+
icon.style.width = '18px';
|
|
1097
|
+
icon.style.height = '18px';
|
|
1098
|
+
iconContainer.appendChild(icon);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
catch (e) {
|
|
1103
|
+
console.error("Toggle screen share failed", e);
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const handleSettingsToggle = () => {
|
|
1107
|
+
setIsSettingsOpen(!isSettingsOpen);
|
|
1108
|
+
};
|
|
1109
|
+
// Update functions
|
|
1110
|
+
const setIsOpen = (open) => {
|
|
1111
|
+
isOpen = open;
|
|
1112
|
+
if (open) {
|
|
1113
|
+
morphingContainer.style.width = '400px';
|
|
1114
|
+
morphingContainer.style.height = '600px';
|
|
1115
|
+
morphingContainer.style.borderRadius = aeroTheme.borderRadius.xl;
|
|
1116
|
+
morphingContainer.style.cursor = 'default';
|
|
1117
|
+
buttonContent.style.opacity = '0';
|
|
1118
|
+
buttonContent.style.transform = 'scale(0.8)';
|
|
1119
|
+
buttonContent.style.pointerEvents = 'none';
|
|
1120
|
+
chatContent.style.opacity = '1';
|
|
1121
|
+
chatContent.style.transform = 'scale(1)';
|
|
1122
|
+
chatContent.style.pointerEvents = 'auto';
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
morphingContainer.style.width = '4rem';
|
|
1126
|
+
morphingContainer.style.height = '4rem';
|
|
1127
|
+
morphingContainer.style.borderRadius = '50%';
|
|
1128
|
+
morphingContainer.style.cursor = 'pointer';
|
|
1129
|
+
buttonContent.style.opacity = '1';
|
|
1130
|
+
buttonContent.style.transform = 'scale(1)';
|
|
1131
|
+
buttonContent.style.pointerEvents = 'auto';
|
|
1132
|
+
chatContent.style.opacity = '0';
|
|
1133
|
+
chatContent.style.transform = 'scale(0.8)';
|
|
1134
|
+
chatContent.style.pointerEvents = 'none';
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
const setIsSettingsOpen = (open) => {
|
|
1138
|
+
isSettingsOpen = open;
|
|
1139
|
+
settingsTray.style.display = open ? 'flex' : 'none';
|
|
1140
|
+
};
|
|
1141
|
+
const updateSendButton = () => {
|
|
1142
|
+
const sendButton = document.getElementById('convai-send-button');
|
|
1143
|
+
if (!sendButton)
|
|
1144
|
+
return;
|
|
1145
|
+
// Clear previous content
|
|
1146
|
+
sendButton.innerHTML = '';
|
|
1147
|
+
if (inputValue.length > 0) {
|
|
1148
|
+
// Show Send Button
|
|
1149
|
+
sendButton.appendChild(Icons.Send('md'));
|
|
1150
|
+
sendButton.style.background = aeroTheme.colors.text.primary;
|
|
1151
|
+
sendButton.style.color = 'white';
|
|
1152
|
+
sendButton.style.border = 'none';
|
|
1153
|
+
sendButton.title = "Send";
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
// Show Voice Mode Toggle
|
|
1157
|
+
sendButton.appendChild(Icons.Waveform('md'));
|
|
1158
|
+
sendButton.style.background = 'transparent';
|
|
1159
|
+
sendButton.style.color = aeroTheme.colors.text.primary;
|
|
1160
|
+
sendButton.style.border = `1px solid ${aeroTheme.colors.neutral[300]}`;
|
|
1161
|
+
sendButton.title = "Voice Mode";
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const updateVideoTrack = () => {
|
|
1165
|
+
if (!floatingVideo || !convaiClient.room || !convaiClient.room.localParticipant)
|
|
1166
|
+
return;
|
|
1167
|
+
const videoEl = floatingVideo.querySelector('#floating-video-element');
|
|
1168
|
+
const placeholder = floatingVideo.querySelector('#camera-off-placeholder');
|
|
1169
|
+
if (!videoEl || !placeholder)
|
|
1170
|
+
return;
|
|
1171
|
+
if (isVideoVisible && !isVoiceMode) {
|
|
1172
|
+
const tracks = Array.from(convaiClient.room.localParticipant.videoTrackPublications.values());
|
|
1173
|
+
const videoPub = tracks.find((t) => t.kind === 'video');
|
|
1174
|
+
if (videoPub && videoPub.track) {
|
|
1175
|
+
videoPub.track.attach(videoEl);
|
|
1176
|
+
videoEl.style.display = 'block';
|
|
1177
|
+
placeholder.style.display = 'none';
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
videoEl.style.display = 'none';
|
|
1181
|
+
placeholder.style.display = 'flex';
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
videoEl.style.display = 'none';
|
|
1186
|
+
placeholder.style.display = 'flex';
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
const updateVoiceMode = () => {
|
|
1190
|
+
const standardFooter = document.getElementById('convai-footer-standard');
|
|
1191
|
+
const voiceExitBtn = document.getElementById('convai-voice-exit-btn');
|
|
1192
|
+
if (isVoiceMode) {
|
|
1193
|
+
// Show Voice Overlay
|
|
1194
|
+
if (voiceModeOverlay)
|
|
1195
|
+
voiceModeOverlay.style.display = 'flex';
|
|
1196
|
+
if (messageListElement)
|
|
1197
|
+
messageListElement.style.display = 'none';
|
|
1198
|
+
if (floatingVideo)
|
|
1199
|
+
floatingVideo.style.display = 'none';
|
|
1200
|
+
// Footer: Show Exit Button, Hide Standard
|
|
1201
|
+
if (standardFooter)
|
|
1202
|
+
standardFooter.style.display = 'none';
|
|
1203
|
+
if (voiceExitBtn)
|
|
1204
|
+
voiceExitBtn.style.display = 'flex';
|
|
1205
|
+
if (footerElement)
|
|
1206
|
+
footerElement.style.justifyContent = 'center';
|
|
1207
|
+
// Update header to show VOICE badge
|
|
1208
|
+
updateHeader();
|
|
1209
|
+
// Reset animation state for voice mode
|
|
1210
|
+
startTime = Date.now();
|
|
1211
|
+
currentLevels = Array(40).fill(0.05);
|
|
1212
|
+
targetLevels = Array(40).fill(0.05);
|
|
1213
|
+
// Start Audio Analysis
|
|
1214
|
+
startAudioAnalysis();
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
// Hide Overlay
|
|
1218
|
+
if (voiceModeOverlay)
|
|
1219
|
+
voiceModeOverlay.style.display = 'none';
|
|
1220
|
+
// Show Video or Message List
|
|
1221
|
+
if (isVideoVisible) {
|
|
1222
|
+
if (messageListElement)
|
|
1223
|
+
messageListElement.style.display = 'none';
|
|
1224
|
+
if (floatingVideo)
|
|
1225
|
+
floatingVideo.style.display = 'block';
|
|
1226
|
+
updateVideoTrack();
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
if (messageListElement)
|
|
1230
|
+
messageListElement.style.display = 'flex';
|
|
1231
|
+
if (floatingVideo)
|
|
1232
|
+
floatingVideo.style.display = 'none';
|
|
1233
|
+
}
|
|
1234
|
+
// Footer: Show Standard, Hide Exit
|
|
1235
|
+
if (standardFooter)
|
|
1236
|
+
standardFooter.style.display = 'flex';
|
|
1237
|
+
if (voiceExitBtn)
|
|
1238
|
+
voiceExitBtn.style.display = 'none';
|
|
1239
|
+
if (footerElement)
|
|
1240
|
+
footerElement.style.justifyContent = 'flex-start';
|
|
1241
|
+
// Update header to hide VOICE badge
|
|
1242
|
+
updateHeader();
|
|
1243
|
+
// Stop Audio Analysis
|
|
1244
|
+
stopAudioAnalysis();
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
const updateMicButton = () => {
|
|
1248
|
+
// Update mic button appearance based on mute state
|
|
1249
|
+
const micButton = document.getElementById('convai-mic-button');
|
|
1250
|
+
if (micButton) {
|
|
1251
|
+
micButton.style.background = convaiClient.audioControls.isAudioMuted
|
|
1252
|
+
? aeroTheme.colors.error[500]
|
|
1253
|
+
: aeroTheme.colors.text.primary;
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
// Helper for Markdown - matches React MarkdownRenderer.tsx
|
|
1257
|
+
const renderMarkdown = (text, container) => {
|
|
1258
|
+
if (!text)
|
|
1259
|
+
return;
|
|
1260
|
+
// Handle both actual newlines and \n escape sequences
|
|
1261
|
+
const normalizedText = text.replace(/\\n/g, '\n');
|
|
1262
|
+
const lines = normalizedText.split('\n');
|
|
1263
|
+
lines.forEach((line, lineIndex) => {
|
|
1264
|
+
// Process markdown within each line
|
|
1265
|
+
const processLine = (text) => {
|
|
1266
|
+
const parts = [];
|
|
1267
|
+
let remaining = text;
|
|
1268
|
+
// Process bold and italic markdown
|
|
1269
|
+
// Bold: **text** or __text__
|
|
1270
|
+
// Italic: *text* or _text_
|
|
1271
|
+
const markdownRegex = /(\*\*|__)(.*?)\1|\*([^*]+)\*|_([^_]+)_/g;
|
|
1272
|
+
let lastIndex = 0;
|
|
1273
|
+
let match;
|
|
1274
|
+
while ((match = markdownRegex.exec(remaining)) !== null) {
|
|
1275
|
+
// Add text before the match
|
|
1276
|
+
if (match.index > lastIndex) {
|
|
1277
|
+
const textBefore = remaining.substring(lastIndex, match.index);
|
|
1278
|
+
if (textBefore) {
|
|
1279
|
+
parts.push(document.createTextNode(textBefore));
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
// Determine if it's bold or italic
|
|
1283
|
+
if (match[1] && match[2]) {
|
|
1284
|
+
// Bold text (** or __)
|
|
1285
|
+
const strong = document.createElement('strong');
|
|
1286
|
+
strong.textContent = match[2];
|
|
1287
|
+
parts.push(strong);
|
|
1288
|
+
}
|
|
1289
|
+
else if (match[3]) {
|
|
1290
|
+
// Italic text (*)
|
|
1291
|
+
const em = document.createElement('em');
|
|
1292
|
+
em.textContent = match[3];
|
|
1293
|
+
parts.push(em);
|
|
1294
|
+
}
|
|
1295
|
+
else if (match[4]) {
|
|
1296
|
+
// Italic text (_)
|
|
1297
|
+
const em = document.createElement('em');
|
|
1298
|
+
em.textContent = match[4];
|
|
1299
|
+
parts.push(em);
|
|
1300
|
+
}
|
|
1301
|
+
lastIndex = match.index + match[0].length;
|
|
1302
|
+
}
|
|
1303
|
+
// Add remaining text
|
|
1304
|
+
if (lastIndex < remaining.length) {
|
|
1305
|
+
const textAfter = remaining.substring(lastIndex);
|
|
1306
|
+
if (textAfter) {
|
|
1307
|
+
parts.push(document.createTextNode(textAfter));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return parts;
|
|
1311
|
+
};
|
|
1312
|
+
const processedLine = processLine(line);
|
|
1313
|
+
// Handle empty lines (creates paragraph spacing)
|
|
1314
|
+
if (line.trim() === "" && lineIndex > 0) {
|
|
1315
|
+
container.appendChild(document.createElement('br'));
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
// Append the processed line
|
|
1319
|
+
if (processedLine.length > 0) {
|
|
1320
|
+
processedLine.forEach(part => container.appendChild(part));
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
container.appendChild(document.createTextNode(line));
|
|
1324
|
+
}
|
|
1325
|
+
// Add line break between lines
|
|
1326
|
+
if (lineIndex < lines.length - 1) {
|
|
1327
|
+
container.appendChild(document.createElement('br'));
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
};
|
|
1331
|
+
const updateMessageList = () => {
|
|
1332
|
+
if (!messageListElement)
|
|
1333
|
+
return;
|
|
1334
|
+
const messages = formatMessages();
|
|
1335
|
+
messageListElement.innerHTML = '';
|
|
1336
|
+
if (messages.length === 0) {
|
|
1337
|
+
const emptyState = document.createElement('div');
|
|
1338
|
+
emptyState.style.cssText = `
|
|
1339
|
+
display: flex;
|
|
1340
|
+
flex-direction: column;
|
|
1341
|
+
align-items: center;
|
|
1342
|
+
justify-content: center;
|
|
1343
|
+
height: 100%;
|
|
1344
|
+
color: ${aeroTheme.colors.text.secondary};
|
|
1345
|
+
text-align: center;
|
|
1346
|
+
padding: 2rem;
|
|
1347
|
+
`;
|
|
1348
|
+
emptyState.innerHTML = `
|
|
1349
|
+
<p style="font-weight: 500; margin-bottom: 8px;">Start a conversation</p>
|
|
1350
|
+
<p style="font-size: 12px;">Type a message below to begin chatting</p>
|
|
1351
|
+
`;
|
|
1352
|
+
messageListElement.appendChild(emptyState);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
messages.forEach((msg) => {
|
|
1356
|
+
const messageWrapper = document.createElement('div');
|
|
1357
|
+
messageWrapper.style.cssText = `
|
|
1358
|
+
display: flex;
|
|
1359
|
+
flex-direction: column;
|
|
1360
|
+
align-items: ${msg.isUser ? 'flex-end' : 'flex-start'};
|
|
1361
|
+
gap: 4px;
|
|
1362
|
+
width: 100%;
|
|
1363
|
+
`;
|
|
1364
|
+
const bubble = document.createElement('div');
|
|
1365
|
+
bubble.style.cssText = `
|
|
1366
|
+
padding: 12px 16px;
|
|
1367
|
+
border-radius: 16px;
|
|
1368
|
+
background: ${msg.isUser ? aeroTheme.colors.convai.light : aeroTheme.colors.neutral[100]};
|
|
1369
|
+
color: ${msg.isUser ? 'white' : aeroTheme.colors.text.primary};
|
|
1370
|
+
font-size: ${aeroTheme.typography.fontSize.sm};
|
|
1371
|
+
line-height: 1.5;
|
|
1372
|
+
word-wrap: break-word;
|
|
1373
|
+
box-shadow: ${aeroTheme.shadows.sm};
|
|
1374
|
+
border-bottom-${msg.isUser ? 'right' : 'left'}-radius: 4px;
|
|
1375
|
+
max-width: 85%;
|
|
1376
|
+
display: flex;
|
|
1377
|
+
flex-direction: column;
|
|
1378
|
+
gap: 8px;
|
|
1379
|
+
`;
|
|
1380
|
+
// Header (Logo/Icon + Name) inside bubble
|
|
1381
|
+
if (msg.sender) {
|
|
1382
|
+
const header = document.createElement('div');
|
|
1383
|
+
header.style.cssText = `
|
|
1384
|
+
display: flex;
|
|
1385
|
+
align-items: center;
|
|
1386
|
+
gap: 6px;
|
|
1387
|
+
margin-bottom: 2px;
|
|
1388
|
+
`;
|
|
1389
|
+
// Logo/Icon
|
|
1390
|
+
const logoContainer = document.createElement('div');
|
|
1391
|
+
logoContainer.style.cssText = `
|
|
1392
|
+
width: 16px;
|
|
1393
|
+
height: 16px;
|
|
1394
|
+
display: flex;
|
|
1395
|
+
align-items: center;
|
|
1396
|
+
justify-content: center;
|
|
1397
|
+
flex-shrink: 0;
|
|
1398
|
+
`;
|
|
1399
|
+
if (msg.isUser) {
|
|
1400
|
+
// User Icon - FaUser from react-icons/fa
|
|
1401
|
+
const userIcon = Icons.User('sm');
|
|
1402
|
+
userIcon.style.width = '16px';
|
|
1403
|
+
userIcon.style.height = '16px';
|
|
1404
|
+
userIcon.style.color = aeroTheme.colors.convai.dark;
|
|
1405
|
+
logoContainer.appendChild(userIcon);
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
// Bot Logo - ConvaiLogo sm size with connected state
|
|
1409
|
+
const botLogo = Icons.ConvaiLogo('sm', 'connected');
|
|
1410
|
+
botLogo.style.width = '16px';
|
|
1411
|
+
botLogo.style.height = '16px';
|
|
1412
|
+
botLogo.style.color = '#10b981';
|
|
1413
|
+
logoContainer.appendChild(botLogo);
|
|
1414
|
+
}
|
|
1415
|
+
const nameLabel = document.createElement('span');
|
|
1416
|
+
nameLabel.textContent = msg.sender === "User" ? "You" : msg.sender;
|
|
1417
|
+
nameLabel.style.cssText = `
|
|
1418
|
+
font-size: 11px;
|
|
1419
|
+
font-weight: 600;
|
|
1420
|
+
color: ${msg.isUser ? 'rgba(255,255,255,0.9)' : aeroTheme.colors.text.primary};
|
|
1421
|
+
opacity: 0.9;
|
|
1422
|
+
`;
|
|
1423
|
+
header.appendChild(logoContainer);
|
|
1424
|
+
header.appendChild(nameLabel);
|
|
1425
|
+
bubble.appendChild(header);
|
|
1426
|
+
}
|
|
1427
|
+
// Message Content
|
|
1428
|
+
const contentDiv = document.createElement('div');
|
|
1429
|
+
contentDiv.style.cssText = `
|
|
1430
|
+
white-space: pre-wrap;
|
|
1431
|
+
word-wrap: break-word;
|
|
1432
|
+
`;
|
|
1433
|
+
renderMarkdown(msg.text, contentDiv);
|
|
1434
|
+
bubble.appendChild(contentDiv);
|
|
1435
|
+
messageWrapper.appendChild(bubble);
|
|
1436
|
+
// Timestamp
|
|
1437
|
+
const timestamp = document.createElement('div');
|
|
1438
|
+
timestamp.textContent = msg.timestamp;
|
|
1439
|
+
timestamp.style.cssText = `
|
|
1440
|
+
font-size: 10px;
|
|
1441
|
+
color: ${aeroTheme.colors.text.secondary};
|
|
1442
|
+
margin-top: 4px;
|
|
1443
|
+
align-self: ${msg.isUser ? 'flex-end' : 'flex-start'};
|
|
1444
|
+
font-family: ${aeroTheme.typography.fontFamily.body};
|
|
1445
|
+
`;
|
|
1446
|
+
messageWrapper.appendChild(timestamp);
|
|
1447
|
+
messageListElement.appendChild(messageWrapper);
|
|
1448
|
+
});
|
|
1449
|
+
// Scroll to bottom
|
|
1450
|
+
messageListElement.scrollTop = messageListElement.scrollHeight;
|
|
1451
|
+
};
|
|
1452
|
+
const getBotStatusColor = () => {
|
|
1453
|
+
if (!convaiClient.state.isConnected) {
|
|
1454
|
+
return { color: "#ef4444", label: "Disconnected" };
|
|
1455
|
+
}
|
|
1456
|
+
if (convaiClient.state.isConnecting || !convaiClient.isBotReady) {
|
|
1457
|
+
return { color: "#f59e0b", label: "Connecting" };
|
|
1458
|
+
}
|
|
1459
|
+
return { color: "#10b981", label: "Connected" };
|
|
1460
|
+
};
|
|
1461
|
+
const formatMessages = () => {
|
|
1462
|
+
const filteredMessages = convaiClient.chatMessages.filter((msg) => (msg.type === "user-transcription" ||
|
|
1463
|
+
msg.type === "user-llm-text" ||
|
|
1464
|
+
msg.type === "bot-llm-text" ||
|
|
1465
|
+
msg.type === "bot-emotion") &&
|
|
1466
|
+
msg.content &&
|
|
1467
|
+
msg.content.trim().length > 0);
|
|
1468
|
+
return filteredMessages.map((msg) => ({
|
|
1469
|
+
id: msg.id,
|
|
1470
|
+
text: msg.content,
|
|
1471
|
+
isUser: msg.type.includes("user"),
|
|
1472
|
+
timestamp: new Date(msg.timestamp).toLocaleTimeString([], {
|
|
1473
|
+
hour: "2-digit",
|
|
1474
|
+
minute: "2-digit",
|
|
1475
|
+
}),
|
|
1476
|
+
sender: msg.type.includes("user") ? "You" : characterName,
|
|
1477
|
+
showLogo: msg.type.includes("bot"),
|
|
1478
|
+
}));
|
|
1479
|
+
};
|
|
1480
|
+
// Setup event listeners for client state changes
|
|
1481
|
+
const setupClientListeners = () => {
|
|
1482
|
+
convaiClient.on('stateChange', () => {
|
|
1483
|
+
updateHeader();
|
|
1484
|
+
updateMicButton();
|
|
1485
|
+
// Update speaking animation state
|
|
1486
|
+
if (convaiClient.state.isSpeaking) {
|
|
1487
|
+
if (!startTime) {
|
|
1488
|
+
startTime = Date.now();
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
// Reset when not speaking
|
|
1493
|
+
if (startTime && !convaiClient.state.isSpeaking) {
|
|
1494
|
+
startTime = 0;
|
|
1495
|
+
currentLevels = Array(40).fill(0.05);
|
|
1496
|
+
targetLevels = Array(40).fill(0.05);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
// Sync video visibility with controls state
|
|
1500
|
+
if (isVideoVisible !== convaiClient.videoControls.isVideoEnabled) {
|
|
1501
|
+
isVideoVisible = convaiClient.videoControls.isVideoEnabled;
|
|
1502
|
+
updateVoiceMode();
|
|
1503
|
+
}
|
|
1504
|
+
// Auto-collapse when disconnected
|
|
1505
|
+
if (!convaiClient.state.isConnected && !convaiClient.state.isConnecting) {
|
|
1506
|
+
if (isOpen) {
|
|
1507
|
+
setIsOpen(false);
|
|
1508
|
+
}
|
|
1509
|
+
isMuted = false;
|
|
1510
|
+
isVoiceMode = false;
|
|
1511
|
+
updateHeader();
|
|
1512
|
+
updateVoiceMode();
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
convaiClient.on('messagesChange', () => {
|
|
1516
|
+
updateMessageList();
|
|
1517
|
+
});
|
|
1518
|
+
convaiClient.on('connect', () => {
|
|
1519
|
+
// Initialize audio renderer on connection
|
|
1520
|
+
if (convaiClient.room && !audioRenderer) {
|
|
1521
|
+
audioRenderer = new AudioRenderer(convaiClient.room);
|
|
1522
|
+
}
|
|
1523
|
+
// Fetch character info
|
|
1524
|
+
fetchCharacterInfo();
|
|
1525
|
+
});
|
|
1526
|
+
convaiClient.on('disconnect', () => {
|
|
1527
|
+
// Cleanup audio renderer
|
|
1528
|
+
if (audioRenderer) {
|
|
1529
|
+
audioRenderer.destroy();
|
|
1530
|
+
audioRenderer = null;
|
|
1531
|
+
}
|
|
1532
|
+
updateMessageList();
|
|
1533
|
+
});
|
|
1534
|
+
};
|
|
1535
|
+
// Initialize
|
|
1536
|
+
createDOM();
|
|
1537
|
+
setupClientListeners();
|
|
1538
|
+
// If already connected, initialize audio renderer and fetch character info
|
|
1539
|
+
if (convaiClient.state.isConnected && convaiClient.room) {
|
|
1540
|
+
audioRenderer = new AudioRenderer(convaiClient.room);
|
|
1541
|
+
fetchCharacterInfo();
|
|
1542
|
+
}
|
|
1543
|
+
// Return widget instance
|
|
1544
|
+
const widget = {
|
|
1545
|
+
element: rootElement,
|
|
1546
|
+
destroy: () => {
|
|
1547
|
+
// Cleanup audio renderer
|
|
1548
|
+
if (audioRenderer) {
|
|
1549
|
+
audioRenderer.destroy();
|
|
1550
|
+
audioRenderer = null;
|
|
1551
|
+
}
|
|
1552
|
+
// Remove event listeners
|
|
1553
|
+
morphingContainer.removeEventListener('click', handleToggle);
|
|
1554
|
+
// Remove DOM elements
|
|
1555
|
+
if (rootElement.parentElement) {
|
|
1556
|
+
rootElement.remove();
|
|
1557
|
+
}
|
|
1558
|
+
if (floatingVideo.parentElement) {
|
|
1559
|
+
floatingVideo.remove();
|
|
1560
|
+
}
|
|
1561
|
+
// Unsubscribe from client events
|
|
1562
|
+
convaiClient.off('stateChange', () => { });
|
|
1563
|
+
convaiClient.off('messagesChange', () => { });
|
|
1564
|
+
convaiClient.off('connect', () => { });
|
|
1565
|
+
convaiClient.off('disconnect', () => { });
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
return widget;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Destroy a Convai widget instance
|
|
1572
|
+
* @param widget - Widget instance to destroy
|
|
1573
|
+
*/
|
|
1574
|
+
export function destroyConvaiWidget(widget) {
|
|
1575
|
+
widget.destroy();
|
|
1576
|
+
}
|
|
1577
|
+
//# sourceMappingURL=ConvaiWidget.js.map
|