286 lines
8.7 KiB
TypeScript
286 lines
8.7 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { useRoomContext } from '@livekit/components-react';
|
|
import { useSubtitleSettings, SubtitleSettings } from './SubtitlesOverlay';
|
|
|
|
function EmailPopup({
|
|
onSkip,
|
|
onSubscribe,
|
|
onClose,
|
|
isLoading,
|
|
error,
|
|
}: {
|
|
onSkip: () => void;
|
|
onSubscribe: (email: string) => void;
|
|
onClose: () => void;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
}) {
|
|
const [email, setEmail] = React.useState(() => {
|
|
if (typeof window !== 'undefined') {
|
|
return localStorage.getItem('summary-email') || '';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
const handleSubscribe = () => {
|
|
if (email.trim()) {
|
|
localStorage.setItem('summary-email', email.trim());
|
|
onSubscribe(email.trim());
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
position: 'absolute',
|
|
top: '100%',
|
|
left: '50%',
|
|
transform: 'translateX(-50%)',
|
|
marginTop: '0.5rem',
|
|
zIndex: 100,
|
|
background: 'var(--lk-bg2, #1a1a1a)',
|
|
border: '1px solid var(--lk-border-color, rgba(255,255,255,0.15))',
|
|
borderRadius: '0.5rem',
|
|
padding: '1rem',
|
|
minWidth: '280px',
|
|
boxShadow: '0 4px 20px rgba(0,0,0,0.4)',
|
|
}}
|
|
>
|
|
<p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'rgba(255,255,255,0.9)' }}>
|
|
Want to receive a summary of this call?
|
|
</p>
|
|
<input
|
|
type="email"
|
|
placeholder="your@email.com (optional)"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
disabled={isLoading}
|
|
style={{
|
|
width: '100%',
|
|
padding: '0.5rem 0.75rem',
|
|
background: 'var(--lk-bg, #111)',
|
|
border: '1px solid var(--lk-border-color, rgba(255,255,255,0.15))',
|
|
borderRadius: '0.375rem',
|
|
color: 'white',
|
|
fontSize: '0.875rem',
|
|
marginBottom: '0.75rem',
|
|
outline: 'none',
|
|
boxSizing: 'border-box',
|
|
}}
|
|
/>
|
|
{error && (
|
|
<p style={{ color: '#ff4444', fontSize: '0.75rem', margin: '0 0 0.5rem' }}>{error}</p>
|
|
)}
|
|
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
<button
|
|
className="lk-button"
|
|
onClick={onSkip}
|
|
disabled={isLoading}
|
|
style={{ flex: 1, fontSize: '0.8125rem' }}
|
|
>
|
|
{isLoading ? 'Loading...' : 'Skip'}
|
|
</button>
|
|
<button
|
|
className="lk-button"
|
|
onClick={handleSubscribe}
|
|
disabled={isLoading || !email.trim()}
|
|
style={{
|
|
flex: 1,
|
|
fontSize: '0.8125rem',
|
|
background: 'var(--lk-accent-bg, #ff6352)',
|
|
border: 'none',
|
|
}}
|
|
>
|
|
{isLoading ? 'Loading...' : 'Subscribe'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SubtitlesSettings() {
|
|
const room = useRoomContext();
|
|
const { settings, updateSettings, hasAgent, summaryEmail, setSummaryEmail } =
|
|
useSubtitleSettings();
|
|
const [showPopup, setShowPopup] = React.useState(false);
|
|
const [isSpawning, setIsSpawning] = React.useState(false);
|
|
const [spawnError, setSpawnError] = React.useState<string | null>(null);
|
|
|
|
const getPassphrase = () => {
|
|
if (typeof window !== 'undefined') {
|
|
const hash = window.location.hash;
|
|
return hash?.length > 1 ? hash.substring(1) : undefined;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const spawnAgent = async (email?: string) => {
|
|
if (!room?.name) {
|
|
setSpawnError('Room not available');
|
|
return false;
|
|
}
|
|
|
|
setIsSpawning(true);
|
|
setSpawnError(null);
|
|
|
|
try {
|
|
const payload = {
|
|
roomName: room.name,
|
|
email: email || undefined,
|
|
e2eePassphrase: getPassphrase(),
|
|
};
|
|
console.log('[SubtitlesSettings] Spawning agent with:', payload);
|
|
|
|
const response = await fetch('/api/agent/spawn', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error((await response.text()) || 'Failed to spawn agent');
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
setSpawnError(error instanceof Error ? error.message : 'Failed');
|
|
return false;
|
|
} finally {
|
|
setIsSpawning(false);
|
|
}
|
|
};
|
|
|
|
const handleToggle = () => {
|
|
if (settings.enabled) {
|
|
updateSettings({ ...settings, enabled: false });
|
|
} else if (hasAgent) {
|
|
updateSettings({ ...settings, enabled: true });
|
|
} else {
|
|
setShowPopup(true);
|
|
setSpawnError(null);
|
|
}
|
|
};
|
|
|
|
const handleSkip = async () => {
|
|
if (await spawnAgent()) {
|
|
setSummaryEmail(null);
|
|
updateSettings({ ...settings, enabled: true });
|
|
setShowPopup(false);
|
|
}
|
|
};
|
|
|
|
const handleSubscribe = async (email: string) => {
|
|
if (await spawnAgent(email)) {
|
|
setSummaryEmail(email);
|
|
updateSettings({ ...settings, enabled: true });
|
|
setShowPopup(false);
|
|
}
|
|
};
|
|
|
|
const updateSetting = <K extends keyof SubtitleSettings>(key: K, value: SubtitleSettings[K]) => {
|
|
updateSettings({ ...settings, [key]: value });
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
|
<h3 style={{ marginBottom: '0.25rem' }}>Subtitles</h3>
|
|
|
|
<section className="lk-button-group">
|
|
<span className="lk-button">Show Subtitles</span>
|
|
<div className="lk-button-group-menu" style={{ position: 'relative' }}>
|
|
<button className="lk-button" aria-pressed={settings.enabled} onClick={handleToggle}>
|
|
{settings.enabled ? 'On' : 'Off'}
|
|
</button>
|
|
{showPopup && (
|
|
<EmailPopup
|
|
onSkip={handleSkip}
|
|
onSubscribe={handleSubscribe}
|
|
onClose={() => !isSpawning && setShowPopup(false)}
|
|
isLoading={isSpawning}
|
|
error={spawnError}
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
{settings.enabled && (
|
|
<>
|
|
<section className="lk-button-group">
|
|
<span className="lk-button">Font Size</span>
|
|
<div className="lk-button-group-menu">
|
|
<select
|
|
className="lk-button"
|
|
value={settings.fontSize}
|
|
onChange={(e) => updateSetting('fontSize', Number(e.target.value))}
|
|
style={{ minWidth: '100px' }}
|
|
>
|
|
<option value={18}>Small</option>
|
|
<option value={24}>Medium</option>
|
|
<option value={32}>Large</option>
|
|
<option value={40}>Extra Large</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="lk-button-group">
|
|
<span className="lk-button">Position</span>
|
|
<div className="lk-button-group-menu">
|
|
<select
|
|
className="lk-button"
|
|
value={settings.position}
|
|
onChange={(e) =>
|
|
updateSetting('position', e.target.value as 'top' | 'center' | 'bottom')
|
|
}
|
|
style={{ minWidth: '100px' }}
|
|
>
|
|
<option value="top">Top</option>
|
|
<option value="center">Center</option>
|
|
<option value="bottom">Bottom</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="lk-button-group">
|
|
<span className="lk-button">Background</span>
|
|
<div className="lk-button-group-menu">
|
|
<select
|
|
className="lk-button"
|
|
value={settings.backgroundColor}
|
|
onChange={(e) => updateSetting('backgroundColor', e.target.value)}
|
|
style={{ minWidth: '100px' }}
|
|
>
|
|
<option value="rgba(0, 0, 0, 0.85)">Dark</option>
|
|
<option value="rgba(0, 0, 0, 0.6)">Medium</option>
|
|
<option value="rgba(0, 0, 0, 0.4)">Light</option>
|
|
<option value="transparent">None</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Summary email status */}
|
|
<div
|
|
style={{
|
|
marginTop: '0.5rem',
|
|
padding: '0.75rem',
|
|
background: 'var(--lk-bg, rgba(0,0,0,0.3))',
|
|
borderRadius: '0.375rem',
|
|
fontSize: '0.8125rem',
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
<span style={{ opacity: 0.7 }}>📧</span>
|
|
<span style={{ opacity: 0.7 }}>Summary:</span>
|
|
{summaryEmail ? (
|
|
<span style={{ color: 'var(--lk-accent-bg, #1fd5f9)' }}>{summaryEmail}</span>
|
|
) : (
|
|
<span style={{ opacity: 0.5, fontStyle: 'italic' }}>No email set</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|