Auto-deploy: 2026-06-02 18:15:27
This commit is contained in:
121
claude-arabic-voice/speech.js
Normal file
121
claude-arabic-voice/speech.js
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user