whung99
feat: deploy Oppy with Google API integration
0d37119
import { useState, useEffect, useRef } from 'react';
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || (import.meta.env.DEV ? 'http://localhost:8000' : '');
export default function BriefPanel({ briefText }) {
const [displayedText, setDisplayedText] = useState('');
const [isPlaying, setIsPlaying] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const audioRef = useRef(null);
useEffect(() => {
if (!briefText) {
setDisplayedText('');
return;
}
// Typewriter effect
setDisplayedText('');
let i = 0;
const interval = setInterval(() => {
if (i < briefText.length) {
setDisplayedText(briefText.slice(0, i + 1));
i++;
} else {
clearInterval(interval);
}
}, 12);
// Auto-launch TTS in parallel
(async () => {
setIsLoading(true);
try {
const res = await fetch(`${BACKEND_URL}/api/tts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: briefText }),
});
if (!res.ok) throw new Error('TTS failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audioRef.current = audio;
audio.onended = () => {
setIsPlaying(false);
URL.revokeObjectURL(url);
};
setIsPlaying(true);
await audio.play();
} catch (err) {
console.error('TTS auto-play error:', err);
} finally {
setIsLoading(false);
}
})();
return () => clearInterval(interval);
}, [briefText]);
const handleSpeak = async () => {
if (isPlaying) {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
setIsPlaying(false);
return;
}
setIsLoading(true);
try {
const res = await fetch(`${BACKEND_URL}/api/tts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: briefText }),
});
if (!res.ok) throw new Error('TTS failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audioRef.current = audio;
audio.onended = () => {
setIsPlaying(false);
URL.revokeObjectURL(url);
};
setIsPlaying(true);
await audio.play();
} catch (err) {
console.error('TTS error:', err);
} finally {
setIsLoading(false);
}
};
if (!briefText) {
return null;
}
return (
<div style={{
background: 'var(--card-bg)',
padding: '24px',
borderTop: '1px solid var(--border)',
animation: 'fadeIn 0.5s ease-out',
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
}}>
<h4 style={{ fontSize: '11px', color: 'var(--text-dim)', letterSpacing: '2px' }}>
BRIEF PROACTIF
</h4>
<button
onClick={handleSpeak}
disabled={isLoading}
style={{
background: 'none',
border: '1px solid var(--green)',
color: 'var(--green)',
padding: '6px 16px',
fontFamily: 'inherit',
fontSize: '12px',
cursor: isLoading ? 'wait' : 'pointer',
transition: 'all 0.2s',
opacity: isLoading ? 0.6 : 1,
}}
>
{isLoading ? '\u23F3 Chargement...' : isPlaying ? '\u23F9 Stop' : '\uD83D\uDD0A Play'}
</button>
</div>
<div style={{
fontSize: '14px',
lineHeight: '1.7',
whiteSpace: 'pre-wrap',
color: '#e0e0e0',
}}>
{displayedText}
{displayedText.length < (briefText?.length || 0) && (
<span style={{ animation: 'pulse 0.8s infinite', color: 'var(--green)' }}>|</span>
)}
</div>
</div>
);
}