Auto-deploy: 2026-06-02 18:34:02
This commit is contained in:
@@ -4,7 +4,25 @@
|
||||
const GEMINI_MODEL = 'gemini-flash-lite-latest';
|
||||
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`;
|
||||
|
||||
// (Offscreen document logic removed in favor of iframe approach)
|
||||
// ─── Offscreen Document Management ───────────────────────────────────────────
|
||||
|
||||
async function setupOffscreenDocument(path) {
|
||||
const offscreenUrl = chrome.runtime.getURL(path);
|
||||
const existingContexts = await chrome.runtime.getContexts({
|
||||
contextTypes: ['OFFSCREEN_DOCUMENT'],
|
||||
documentUrls: [offscreenUrl]
|
||||
});
|
||||
|
||||
if (existingContexts.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await chrome.offscreen.createDocument({
|
||||
url: path,
|
||||
reasons: ['USER_MEDIA'],
|
||||
justification: 'Recording microphone input for speech recognition',
|
||||
});
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'GEMINI_REQUEST') {
|
||||
@@ -22,7 +40,32 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
}
|
||||
|
||||
if (message.type === 'OPEN_PERMISSION_PAGE') {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('claude-arabic-voice/permission.html') });
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Offscreen Document Relays for Popup ---
|
||||
if (message.type === 'START_RECORDING_FROM_POPUP') {
|
||||
setupOffscreenDocument('claude-arabic-voice/offscreen.html')
|
||||
.then(() => {
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'START_RECORDING',
|
||||
payload: message.payload
|
||||
}, (response) => {
|
||||
sendResponse(response || { success: true });
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to setup offscreen doc', err);
|
||||
sendResponse({ success: false, error: err.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'STOP_RECORDING_FROM_POPUP') {
|
||||
chrome.runtime.sendMessage({ type: 'STOP_RECORDING' }, (response) => {
|
||||
sendResponse(response || { success: true });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
111
popup.js
111
popup.js
@@ -264,76 +264,22 @@ document.getElementById('autofill-btn').addEventListener('click', async () => {
|
||||
|
||||
// ─── Voice Dictation ───────────────────────────────────────────────────────────
|
||||
|
||||
// ─── Voice Dictation ───────────────────────────────────────────────────────────
|
||||
|
||||
function initDictation() {
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
const statusEl = document.getElementById('dictation-status');
|
||||
const micBtn = document.getElementById('dictation-mic-btn');
|
||||
const resultArea = document.getElementById('dictation-result');
|
||||
const copyBtn = document.getElementById('copy-dictation-btn');
|
||||
|
||||
if (!SpeechRecognition) {
|
||||
statusEl.textContent = '❌ المتصفح لا يدعم التعرف على الصوت';
|
||||
micBtn.disabled = true;
|
||||
micBtn.style.opacity = '0.5';
|
||||
return;
|
||||
}
|
||||
|
||||
let recognition = new SpeechRecognition();
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.lang = 'ar-SA';
|
||||
|
||||
let isRecording = false;
|
||||
let finalTranscript = '';
|
||||
|
||||
recognition.onstart = () => {
|
||||
isRecording = true;
|
||||
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)';
|
||||
micBtn.innerHTML = '🔴';
|
||||
micBtn.style.animation = 'claude-voice-pulse 1.5s infinite'; // We can add this via inline or just rely on color
|
||||
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
|
||||
finalTranscript = '';
|
||||
resultArea.value = '';
|
||||
copyBtn.style.display = 'none';
|
||||
};
|
||||
|
||||
recognition.onresult = (event) => {
|
||||
let interimTranscript = '';
|
||||
let currentFinal = '';
|
||||
|
||||
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||
const transcript = event.results[i][0].transcript;
|
||||
if (event.results[i].isFinal) {
|
||||
currentFinal += transcript + ' ';
|
||||
} else {
|
||||
interimTranscript += transcript;
|
||||
}
|
||||
}
|
||||
|
||||
finalTranscript += currentFinal;
|
||||
resultArea.value = finalTranscript + interimTranscript;
|
||||
};
|
||||
|
||||
recognition.onerror = (event) => {
|
||||
console.error('Speech recognition error', event.error);
|
||||
if (event.error === 'not-allowed') {
|
||||
statusEl.textContent = '❌ يرجى السماح للميكروفون من إعدادات المتصفح';
|
||||
} else if (event.error !== 'no-speech') {
|
||||
statusEl.textContent = '❌ خطأ: ' + event.error;
|
||||
}
|
||||
stopRecording(false);
|
||||
};
|
||||
|
||||
recognition.onend = () => {
|
||||
if (isRecording) {
|
||||
stopRecording(true);
|
||||
}
|
||||
};
|
||||
let interimTranscript = '';
|
||||
|
||||
function stopRecording(process = true) {
|
||||
if (!isRecording) return;
|
||||
isRecording = false;
|
||||
recognition.stop();
|
||||
chrome.runtime.sendMessage({ type: 'STOP_RECORDING_FROM_POPUP' });
|
||||
micBtn.style.background = 'linear-gradient(135deg, var(--accent), #9b5de5)';
|
||||
micBtn.innerHTML = '🎤';
|
||||
|
||||
@@ -346,18 +292,51 @@ function initDictation() {
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to messages from the offscreen document via the background
|
||||
chrome.runtime.onMessage.addListener((message) => {
|
||||
if (message.type === 'OFFSCREEN_RECORDING_RESULT') {
|
||||
interimTranscript = message.payload.interimText || '';
|
||||
finalTranscript = message.payload.finalText || '';
|
||||
resultArea.value = (finalTranscript + interimTranscript).trim();
|
||||
} else if (message.type === 'OFFSCREEN_RECORDING_ERROR') {
|
||||
console.error('Speech recognition error', message.payload.error);
|
||||
if (message.payload.error === 'not-allowed') {
|
||||
statusEl.textContent = '❌ يرجى السماح للميكروفون من إعدادات المتصفح';
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
|
||||
} else if (message.payload.error !== 'no-speech') {
|
||||
statusEl.textContent = '❌ خطأ: ' + message.payload.error;
|
||||
}
|
||||
stopRecording(false);
|
||||
} else if (message.type === 'OFFSCREEN_RECORDING_END') {
|
||||
if (isRecording) {
|
||||
stopRecording(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
micBtn.addEventListener('click', async () => {
|
||||
if (isRecording) {
|
||||
stopRecording(true);
|
||||
} else {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
stream.getTracks().forEach(t => t.stop());
|
||||
recognition.start();
|
||||
} catch (err) {
|
||||
statusEl.textContent = '❌ يجب السماح باستخدام الميكروفون';
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
|
||||
}
|
||||
isRecording = true;
|
||||
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)';
|
||||
micBtn.innerHTML = '🔴';
|
||||
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
|
||||
finalTranscript = '';
|
||||
interimTranscript = '';
|
||||
resultArea.value = '';
|
||||
copyBtn.style.display = 'none';
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'START_RECORDING_FROM_POPUP',
|
||||
payload: { language: 'ar-SA' }
|
||||
}, (response) => {
|
||||
if (!response || !response.success) {
|
||||
console.error('Failed to start recording', response);
|
||||
statusEl.textContent = '❌ فشل بدء التسجيل';
|
||||
stopRecording(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user