122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
// speech.js - Handles webkitSpeechRecognition inside the iframe
|
|
|
|
let recognition = null;
|
|
let isRecording = false;
|
|
let localStream = null;
|
|
|
|
function stopMediaTracks() {
|
|
if (localStream) {
|
|
localStream.getTracks().forEach(track => track.stop());
|
|
localStream = null;
|
|
}
|
|
}
|
|
|
|
function initRecognition(language) {
|
|
if (recognition) return recognition;
|
|
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
if (!SpeechRecognition) {
|
|
throw new Error('Speech recognition not supported');
|
|
}
|
|
|
|
recognition = new SpeechRecognition();
|
|
recognition.continuous = true;
|
|
recognition.interimResults = true;
|
|
recognition.lang = language || 'ar-SA';
|
|
recognition.maxAlternatives = 1;
|
|
|
|
recognition.onresult = (event) => {
|
|
let interimText = '';
|
|
let finalText = '';
|
|
|
|
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
const result = event.results[i];
|
|
if (result.isFinal) {
|
|
finalText += result[0].transcript + ' ';
|
|
} else {
|
|
interimText += result[0].transcript;
|
|
}
|
|
}
|
|
|
|
window.parent.postMessage({
|
|
type: 'SPEECH_RESULT',
|
|
payload: { interimText, finalText }
|
|
}, '*');
|
|
};
|
|
|
|
recognition.onerror = (event) => {
|
|
console.error('[Speech Iframe] Recognition error:', event.error);
|
|
|
|
if (event.error !== 'no-speech') {
|
|
isRecording = false;
|
|
stopMediaTracks();
|
|
}
|
|
|
|
window.parent.postMessage({
|
|
type: 'SPEECH_ERROR',
|
|
payload: { error: event.error }
|
|
}, '*');
|
|
};
|
|
|
|
recognition.onend = () => {
|
|
if (isRecording) {
|
|
try {
|
|
recognition.start();
|
|
} catch (e) {
|
|
console.warn('[Speech Iframe] Restart failed:', e);
|
|
}
|
|
} else {
|
|
stopMediaTracks();
|
|
window.parent.postMessage({ type: 'SPEECH_END' }, '*');
|
|
}
|
|
};
|
|
|
|
return recognition;
|
|
}
|
|
|
|
// Listen for messages from parent window
|
|
window.addEventListener('message', (event) => {
|
|
// Basic security check (only accept messages from the parent Claude page)
|
|
if (!event.origin.includes('claude.ai')) return;
|
|
|
|
const message = event.data;
|
|
|
|
if (message.type === 'START_RECORDING') {
|
|
const lang = message.payload?.language || 'ar-SA';
|
|
|
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
|
.then((stream) => {
|
|
localStream = stream;
|
|
try {
|
|
if (!recognition || recognition.lang !== lang) {
|
|
recognition = initRecognition(lang);
|
|
}
|
|
if (!isRecording) {
|
|
recognition.start();
|
|
isRecording = true;
|
|
window.parent.postMessage({ type: 'SPEECH_START_SUCCESS' }, '*');
|
|
}
|
|
} catch (e) {
|
|
console.error('[Speech Iframe] Failed to start:', e);
|
|
window.parent.postMessage({ type: 'SPEECH_ERROR', payload: { error: e.message } }, '*');
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error('[Speech Iframe] getUserMedia failed:', err);
|
|
window.parent.postMessage({ type: 'SPEECH_ERROR', payload: { error: 'not-allowed' } }, '*');
|
|
});
|
|
}
|
|
|
|
if (message.type === 'STOP_RECORDING') {
|
|
if (isRecording && recognition) {
|
|
isRecording = false;
|
|
try {
|
|
recognition.stop();
|
|
} catch (e) {
|
|
console.warn('[Speech Iframe] Stop failed:', e);
|
|
}
|
|
}
|
|
stopMediaTracks();
|
|
}
|
|
});
|