//
// LiveTranscriptPanel.jsx — Canlı Transkript + Alarm Bileşenleri
//
// Dışa aktarılan bileşenler:
// window.AlarmBanner — Aktif alarmları üst panelde gösterir
// window.LiveTranscriptPanel — Bir çağrının anlık transkriptini listeler
//
const { useState, useEffect, useRef, useCallback } = React;
// Alarm tipi Türkçe etiket ve ikon eşlemesi
const ALARM_META = {
angry_detected: { label: 'Sinirli Müşteri', icon: '', color: '#ef4444' },
score_threshold: { label: 'Yüksek Olumsuzluk', icon: '', color: '#f97316' },
sustained_negative: { label: 'Sürekli Olumsuz', icon: '', color: '#f59e0b' },
rapid_drop: { label: 'Ani Duygu Düşüşü', icon: '', color: '#ef4444' },
};
// Duygu renk ve stil eşlemesi
const EMOTION_STYLE = {
positive: { bg: 'rgba(34,197,94,.15)', border: 'rgba(34,197,94,.4)', color: '#22c55e' },
neutral: { bg: 'rgba(139,148,158,.1)', border: 'var(--border)', color: 'var(--text-dim)' },
negative: { bg: 'rgba(245,158,11,.15)', border: 'rgba(245,158,11,.4)', color: '#f59e0b' },
angry: { bg: 'rgba(239,68,68,.15)', border: 'rgba(239,68,68,.4)', color: '#ef4444' },
};
// Web Audio ile alarm sesi (harici dosya gerektirmez)
function playAlarmSound() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
[0, 0.18, 0.36].forEach(offset => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = 880;
osc.type = 'square';
gain.gain.setValueAtTime(0.12, ctx.currentTime + offset);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + offset + 0.15);
osc.start(ctx.currentTime + offset);
osc.stop(ctx.currentTime + offset + 0.15);
});
} catch (_) {}
}
//
// AlarmBanner — Çözümlenmemiş alarmları üstte kırmızı/turuncu banner olarak gösterir
//
function AlarmBanner({ alarms, onResolve }) {
if (!alarms || alarms.length === 0) return null;
return (
{alarms.map(alarm => {
const meta = ALARM_META[alarm.alarm_type] || { label: alarm.alarm_type, icon: '', color: '#ef4444' };
const emotion = alarm.emotion || {};
const preview = alarm.trigger_text ? `"${alarm.trigger_text.slice(0, 70)}${alarm.trigger_text.length > 70 ? '…' : ''}"` : '';
const callShort = alarm.call_uuid ? alarm.call_uuid.slice(0, 8) : '—';
return (
{/* Sol: ikon + açıklama */}
{meta.icon}
{meta.label}
{emotion.emoji} {emotion.tr} {emotion.score != null ? `%${emotion.score}` : ''}
#{callShort}
{alarm.call_type === 'human' && (
Canlı Temsilci
)}
{preview && (
{preview}
)}
{/* Sağ: zaman + kapat butonu */}
{alarm.triggered_at ? fmtTimeSec(alarm.triggered_at) : ''}
);
})}
);
}
//
// LiveTranscriptPanel — Tek bir çağrının anlık transkriptini gösterir
//
function LiveTranscriptPanel({ callUuid, entries = [], callType = 'ai' }) {
const bottomRef = useRef(null);
// Yeni mesaj gelince otomatik aşağı kaydır
useEffect(() => {
if (bottomRef.current) {
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [entries.length]);
if (entries.length === 0) {
return (
Transkript bekleniyor…
);
}
return (
{entries.map((entry, i) => {
const isCustomer = entry.speaker === 'customer';
const isBot = entry.speaker === 'bot';
const isAgent = entry.speaker === 'agent';
const emotion = entry.emotion;
const emoStyle = emotion ? (EMOTION_STYLE[emotion.label] || EMOTION_STYLE.neutral) : null;
return (
{/* Konuşmacı etiketi */}
{isCustomer ? ' Müşteri' : isBot ? ' Asistan' : ' Temsilci'}
{/* Balon */}
{entry.text}
{/* Duygu badge + zaman */}
{isCustomer && emotion && emotion.label !== 'neutral' && (
{emotion.emoji} {emotion.tr} %{emotion.score}
)}
{entry.timestamp && (
{fmtTimeSec(entry.timestamp)}
)}
);
})}
);
}
// Dışa aktar
window.AlarmBanner = AlarmBanner;
window.LiveTranscriptPanel = LiveTranscriptPanel;
window.playAlarmSound = playAlarmSound;