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_MODEL = 'gemini-flash-lite-latest';
|
||||||
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`;
|
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) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
if (message.type === 'GEMINI_REQUEST') {
|
if (message.type === 'GEMINI_REQUEST') {
|
||||||
@@ -22,7 +40,32 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'OPEN_PERMISSION_PAGE') {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
107
popup.js
107
popup.js
@@ -264,76 +264,22 @@ document.getElementById('autofill-btn').addEventListener('click', async () => {
|
|||||||
|
|
||||||
// ─── Voice Dictation ───────────────────────────────────────────────────────────
|
// ─── Voice Dictation ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// ─── Voice Dictation ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function initDictation() {
|
function initDictation() {
|
||||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
||||||
const statusEl = document.getElementById('dictation-status');
|
const statusEl = document.getElementById('dictation-status');
|
||||||
const micBtn = document.getElementById('dictation-mic-btn');
|
const micBtn = document.getElementById('dictation-mic-btn');
|
||||||
const resultArea = document.getElementById('dictation-result');
|
const resultArea = document.getElementById('dictation-result');
|
||||||
const copyBtn = document.getElementById('copy-dictation-btn');
|
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 isRecording = false;
|
||||||
let finalTranscript = '';
|
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 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function stopRecording(process = true) {
|
function stopRecording(process = true) {
|
||||||
if (!isRecording) return;
|
if (!isRecording) return;
|
||||||
isRecording = false;
|
isRecording = false;
|
||||||
recognition.stop();
|
chrome.runtime.sendMessage({ type: 'STOP_RECORDING_FROM_POPUP' });
|
||||||
micBtn.style.background = 'linear-gradient(135deg, var(--accent), #9b5de5)';
|
micBtn.style.background = 'linear-gradient(135deg, var(--accent), #9b5de5)';
|
||||||
micBtn.innerHTML = '🎤';
|
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 () => {
|
micBtn.addEventListener('click', async () => {
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
stopRecording(true);
|
stopRecording(true);
|
||||||
} else {
|
} else {
|
||||||
try {
|
isRecording = true;
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)';
|
||||||
stream.getTracks().forEach(t => t.stop());
|
micBtn.innerHTML = '🔴';
|
||||||
recognition.start();
|
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
|
||||||
} catch (err) {
|
finalTranscript = '';
|
||||||
statusEl.textContent = '❌ يجب السماح باستخدام الميكروفون';
|
interimTranscript = '';
|
||||||
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
|
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