Files
explorer-monorepo/virtual-banker/widget/src/App.tsx

190 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { ChatPanel } from './components/ChatPanel';
import { VoiceControls } from './components/VoiceControls';
import { AvatarView } from './components/AvatarView';
import { Captions } from './components/Captions';
import { Settings } from './components/Settings';
import { useSession } from './hooks/useSession';
import { useConversation } from './hooks/useConversation';
import { useWebRTC } from './hooks/useWebRTC';
import { PostMessageAPI } from './services/postMessage';
import { WidgetConfig } from './types';
import './App.css';
// Default config - can be overridden via postMessage or data attributes
const getConfig = (): WidgetConfig => {
const script = document.querySelector('script[data-tenant-id]');
if (script) {
return {
tenantId: script.getAttribute('data-tenant-id') || 'default',
userId: script.getAttribute('data-user-id') || undefined,
authToken: script.getAttribute('data-auth-token') || undefined,
apiUrl: script.getAttribute('data-api-url') || undefined,
avatarEnabled: script.getAttribute('data-avatar-enabled') !== 'false',
};
}
return {
tenantId: 'default',
avatarEnabled: true,
};
};
export const App: React.FC = () => {
const [config] = useState<WidgetConfig>(getConfig());
const [showSettings, setShowSettings] = useState(false);
const [showCaptions, setShowCaptions] = useState(true);
const [avatarEnabled, setAvatarEnabled] = useState(config.avatarEnabled ?? true);
const [volume, setVolume] = useState(100);
const [isMuted, setIsMuted] = useState(false);
const [captionText, setCaptionText] = useState('');
const postMessage = new PostMessageAPI();
const { session, loading, error, createSession, endSession } = useSession(config);
const {
messages,
isListening,
isSpeaking,
setIsListening,
setIsSpeaking,
sendMessage,
receiveMessage,
} = useConversation();
const { isConnected, remoteStream, initializeWebRTC, closeWebRTC } = useWebRTC();
// Initialize session on mount
useEffect(() => {
createSession();
}, []);
// Initialize WebRTC when session is ready
useEffect(() => {
if (session && !isConnected) {
initializeWebRTC();
}
}, [session, isConnected]);
// Cleanup on unmount
useEffect(() => {
return () => {
endSession();
closeWebRTC();
};
}, []);
// Send ready event
useEffect(() => {
if (session) {
postMessage.ready();
postMessage.sessionStarted(session.sessionId);
}
}, [session]);
// Listen for messages from host
useEffect(() => {
const unsubscribe = postMessage.on('open', () => {
// Widget opened
});
return unsubscribe;
}, []);
const handleSendMessage = (message: string) => {
sendMessage(message);
// TODO: Send to backend via WebRTC or WebSocket
};
const handlePushToTalk = () => {
setIsListening(true);
// TODO: Start audio capture
};
const handleHandsFree = () => {
setIsListening(true);
// TODO: Enable continuous listening
};
const handleToggleMute = () => {
setIsMuted(!isMuted);
// TODO: Mute/unmute audio
};
if (loading) {
return (
<div className="widget-container loading">
<div className="loading-spinner">Loading...</div>
</div>
);
}
if (error) {
return (
<div className="widget-container error">
<div className="error-message">Error: {error}</div>
<button onClick={createSession}>Retry</button>
</div>
);
}
return (
<div className="widget-container">
<div className="widget-header">
<h1>Virtual Banker</h1>
<button
onClick={() => setShowSettings(true)}
className="settings-button"
aria-label="Settings"
>
</button>
</div>
<div className="widget-content">
{avatarEnabled && (
<div className="widget-avatar-section">
<AvatarView
enabled={avatarEnabled}
videoStream={remoteStream || undefined}
onToggle={() => setAvatarEnabled(false)}
/>
</div>
)}
<div className="widget-chat-section">
<ChatPanel
messages={messages}
onSendMessage={handleSendMessage}
isListening={isListening}
isSpeaking={isSpeaking}
showCaptions={showCaptions}
onToggleCaptions={() => setShowCaptions(!showCaptions)}
/>
</div>
</div>
<VoiceControls
onPushToTalk={handlePushToTalk}
onHandsFree={handleHandsFree}
isListening={isListening}
isMuted={isMuted}
onToggleMute={handleToggleMute}
volume={volume}
onVolumeChange={setVolume}
/>
<Captions text={captionText} visible={showCaptions} />
{showSettings && (
<Settings
showCaptions={showCaptions}
onToggleCaptions={() => setShowCaptions(!showCaptions)}
avatarEnabled={avatarEnabled}
onToggleAvatar={() => setAvatarEnabled(!avatarEnabled)}
onClose={() => setShowSettings(false)}
/>
)}
</div>
);
};