const { useState, useEffect } = React; /* ───────────────────────────────────────────────────────────── AnnouncementLibrary — WAV upload/playback/delete panel shown at the top of the IVR tab ───────────────────────────────────────────────────────────── */ function AnnouncementLibrary({ announcements, onUploaded, onDeleted }) { const [uploading, setUploading] = React.useState(false); const [expanded, setExpanded] = React.useState(true); const fileInputRef = React.useRef(null); const [uploadName, setUploadName] = React.useState(''); const [uploadDesc, setUploadDesc] = React.useState(''); const [selectedFile, setSelectedFile] = React.useState(null); const [playingId, setPlayingId] = React.useState(null); const audioRef = React.useRef(null); function handleFileSelect(e) { const f = e.target.files[0]; if (!f) return; const allowed = ['.wav', '.mp3']; const ext = f.name.toLowerCase().slice(f.name.lastIndexOf('.')); if (!allowed.includes(ext)) { alert('Sadece .wav veya .mp3 dosyaları kabul edilir.'); return; } setSelectedFile(f); if (!uploadName) setUploadName(f.name.replace(/\.(wav|mp3)$/i, '')); } async function handleUpload() { if (!selectedFile || !uploadName.trim()) { alert('Dosya seçin ve anons adı girin.'); return; } setUploading(true); try { const fd = new FormData(); fd.append('name', uploadName.trim()); fd.append('description', uploadDesc.trim()); fd.append('file', selectedFile); const r = await authFetch('/api/announcements/upload', { method: 'POST', body: fd }); if (r.ok) { setSelectedFile(null); setUploadName(''); setUploadDesc(''); fileInputRef.current.value = ''; onUploaded(); } else { const err = await r.json(); alert('Yükleme hatası: ' + (err.detail || JSON.stringify(err))); } } finally { setUploading(false); } } function handlePlay(ann) { if (playingId === ann.id) { audioRef.current?.pause(); setPlayingId(null); return; } if (audioRef.current) { audioRef.current.src = `/api/announcements/${ann.id}/stream`; audioRef.current.play().catch(() => {}); setPlayingId(ann.id); audioRef.current.onended = () => setPlayingId(null); } } async function handleDelete(ann) { if (!confirm(`"${ann.name}" silinsin mi?`)) return; const r = await authFetch(`/api/announcements/${ann.id}`, { method: 'DELETE' }); if (r.ok) onDeleted(); } function fmtSize(bytes) { if (!bytes) return ''; if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1024 / 1024).toFixed(1) + ' MB'; } return (
); } /* ───────────────────────────────────────────────────────────── IVRModal — create / edit a single IVR menu ───────────────────────────────────────────────────────────── */ function IVRModal({ menu, onClose, onSave }) { const emptyForm = { name: '', description: '', greet_text: 'Merhaba, şirketimize hoş geldiniz. Satış için 1\'i, destek için 2\'yi tuşlayınız.', invalid_text: 'Hatalı tuşlama yaptınız. Lütfen tekrar deneyin.', exit_text: 'İşleminizi gerçekleştiremiyoruz. İyi günler dileriz.', timeout_ms: 3000, max_failures: 3, is_active: true, greet_announcement_id: null, invalid_announcement_id: null, exit_announcement_id: null, }; const [form, setForm] = useState(menu ? { ...menu } : emptyForm); const [options, setOptions] = useState(menu?.options ? [...menu.options] : []); const [wsCalls, setWsCalls] = useState(menu?.ws_pre_calls ? [...menu.ws_pre_calls] : []); const [saving, setSaving] = useState(false); const [queues, setQueues] = useState([]); const [agents, setAgents] = useState([]); const [services, setServices] = useState([]); const [announcements, setAnnouncements] = useState([]); const [greetSource, setGreetSource] = useState(menu?.greet_announcement_id ? 'ann' : 'tts'); const [invalidSource, setInvalidSource] = useState(menu?.invalid_announcement_id ? 'ann' : 'tts'); const [exitSource, setExitSource] = useState(menu?.exit_announcement_id ? 'ann' : 'tts'); const [playingPreview, setPlayingPreview] = useState(null); const previewAudioRef = React.useRef(null); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); useEffect(() => { authFetch('/api/queues?active_only=true') .then(r => r.ok ? r.json() : []).then(setQueues).catch(() => {}); authFetch('/api/agents') .then(r => r.ok ? r.json() : []).then(setAgents).catch(() => {}); authFetch('/api/ws/services') .then(r => r.ok ? r.json() : []).then(setServices).catch(() => {}); authFetch('/api/announcements') .then(r => r.ok ? r.json() : []).then(setAnnouncements).catch(() => {}); }, []); function playPreview(annId) { if (playingPreview === annId) { previewAudioRef.current?.pause(); setPlayingPreview(null); return; } if (previewAudioRef.current) { previewAudioRef.current.src = `/api/announcements/${annId}/stream`; previewAudioRef.current.play().catch(() => {}); setPlayingPreview(annId); previewAudioRef.current.onended = () => setPlayingPreview(null); } } /* TTS / Announcement toggle section — defined inside IVRModal so it closes over form, set, playPreview, playingPreview */ function AudioSourceSection({ label, sourceState, setSourceState, textKey, annIdKey, annotations }) { return (
{[['tts', '🔊 TTS'], ['ann', '📢 Anons']].map(([val, lbl]) => ( ))}
{sourceState === 'tts' ? (