feat(korea): 중국어 경고문 TTS 음성 재생 (Web Speech API)
- 경고문 옆 🔊 버튼 클릭 → 중국어(zh-CN) 음성 재생 - SpeechSynthesis API 사용 (브라우저 내장, API 키 불필요) - 재생 중 버튼 애니메이션 (pulse) + 배경 하이라이트 - 재생 속도 0.85x (확성기 방송용 느린 발화) - 클릭: 클립보드 복사 / 🔊: 음성 재생 분리 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
1aa887fce4
커밋
8b74f455df
@ -137,10 +137,29 @@ export function OpsGuideModal({ ships, onClose, onFlyTo, onRouteSelect }: Props)
|
||||
const criticalCount = suspects.filter(s => s.riskLevel === 'CRITICAL').length;
|
||||
const highCount = suspects.filter(s => s.riskLevel === 'HIGH').length;
|
||||
|
||||
const [speakingIdx, setSpeakingIdx] = useState<number | null>(null);
|
||||
|
||||
const copyToClipboard = (text: string, idx: number) => {
|
||||
navigator.clipboard.writeText(text).then(() => { setCopiedIdx(idx); setTimeout(() => setCopiedIdx(null), 1500); });
|
||||
};
|
||||
|
||||
const speakChinese = useCallback((text: string, idx: number) => {
|
||||
if (typeof window === 'undefined' || !window.speechSynthesis) return;
|
||||
window.speechSynthesis.cancel();
|
||||
const utter = new SpeechSynthesisUtterance(text);
|
||||
utter.lang = 'zh-CN';
|
||||
utter.rate = 0.85;
|
||||
utter.volume = 1;
|
||||
// Try to find a Chinese voice
|
||||
const voices = window.speechSynthesis.getVoices();
|
||||
const zhVoice = voices.find(v => v.lang.startsWith('zh')) || voices.find(v => v.lang.includes('CN'));
|
||||
if (zhVoice) utter.voice = zhVoice;
|
||||
utter.onstart = () => setSpeakingIdx(idx);
|
||||
utter.onend = () => setSpeakingIdx(null);
|
||||
utter.onerror = () => setSpeakingIdx(null);
|
||||
window.speechSynthesis.speak(utter);
|
||||
}, []);
|
||||
|
||||
const handleSuspectClick = (s: SuspectVessel) => {
|
||||
setSelectedSuspect(s);
|
||||
setTab('procedure');
|
||||
@ -244,16 +263,35 @@ export function OpsGuideModal({ ships, onClose, onFlyTo, onRouteSelect }: Props)
|
||||
|
||||
{/* 중국어 경고문 */}
|
||||
<div style={{ marginTop: 12, borderTop: '1px solid #1e293b', paddingTop: 8 }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#f59e0b', marginBottom: 6 }}>📢 중국어 경고문 (클릭하여 복사)</div>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#f59e0b', marginBottom: 6 }}>📢 중국어 경고문 (클릭: 복사 | 🔊: 음성)</div>
|
||||
{(CN_WARNINGS[selectedSuspect.estimatedType] || CN_WARNINGS.UNKNOWN).map((w, i) => (
|
||||
<div key={i} onClick={() => copyToClipboard(w.zh, i)} style={{
|
||||
background: copiedIdx === i ? 'rgba(34,197,94,0.15)' : '#111827',
|
||||
border: copiedIdx === i ? '1px solid rgba(34,197,94,0.4)' : '1px solid #1e293b',
|
||||
borderRadius: 4, padding: '6px 10px', marginBottom: 4, cursor: 'pointer', transition: 'all 0.2s',
|
||||
<div key={i} style={{
|
||||
background: copiedIdx === i ? 'rgba(34,197,94,0.15)' : speakingIdx === i ? 'rgba(251,191,36,0.1)' : '#111827',
|
||||
border: copiedIdx === i ? '1px solid rgba(34,197,94,0.4)' : speakingIdx === i ? '1px solid rgba(251,191,36,0.4)' : '1px solid #1e293b',
|
||||
borderRadius: 4, padding: '6px 10px', marginBottom: 4, transition: 'all 0.2s',
|
||||
display: 'flex', alignItems: 'flex-start', gap: 8,
|
||||
}}>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, color: '#fbbf24' }}>{w.zh}</div>
|
||||
<div style={{ fontSize: 9, color: '#94a3b8' }}>{w.ko}</div>
|
||||
<div style={{ fontSize: 8, color: '#475569' }}>사용: {w.usage} {copiedIdx === i && <span style={{ color: '#22c55e', fontWeight: 700 }}>✓ 복사됨</span>}</div>
|
||||
<div style={{ flex: 1, cursor: 'pointer' }} onClick={() => copyToClipboard(w.zh, i)}>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, color: '#fbbf24' }}>{w.zh}</div>
|
||||
<div style={{ fontSize: 9, color: '#94a3b8' }}>{w.ko}</div>
|
||||
<div style={{ fontSize: 8, color: '#475569' }}>
|
||||
사용: {w.usage}
|
||||
{copiedIdx === i && <span style={{ color: '#22c55e', fontWeight: 700, marginLeft: 4 }}>✓ 복사됨</span>}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); speakChinese(w.zh, i); }}
|
||||
style={{
|
||||
background: speakingIdx === i ? 'rgba(251,191,36,0.3)' : 'rgba(251,191,36,0.1)',
|
||||
border: '1px solid rgba(251,191,36,0.3)',
|
||||
borderRadius: 4, padding: '4px 8px', cursor: 'pointer',
|
||||
fontSize: 14, lineHeight: 1, flexShrink: 0,
|
||||
animation: speakingIdx === i ? 'pulse 1s ease-in-out infinite' : 'none',
|
||||
}}
|
||||
title="중국어 음성 재생"
|
||||
>
|
||||
{speakingIdx === i ? '🔊' : '🔈'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user