Auto-deploy: 2026-06-02 18:39:04

This commit is contained in:
Hamza-Ayed
2026-06-02 18:39:04 +03:00
parent 8ebbedad83
commit 979a5bbdae
5 changed files with 186 additions and 63 deletions

View File

@@ -4,25 +4,7 @@
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 Management ─────────────────────────────────────────── // (Offscreen document logic removed in favor of iframe approach)
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') {
@@ -43,31 +25,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') }); chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
return true; 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;
}
}); });
// ─── Core API call ─────────────────────────────────────────────────────────── // ─── Core API call ───────────────────────────────────────────────────────────

View File

@@ -8,8 +8,7 @@
"activeTab", "activeTab",
"scripting", "scripting",
"clipboardWrite", "clipboardWrite",
"microphone", "microphone"
"offscreen"
], ],
"host_permissions": [ "host_permissions": [
"https://www.linkedin.com/*", "https://www.linkedin.com/*",
@@ -54,6 +53,15 @@
"128": "icons/icon128.png" "128": "icons/icon128.png"
} }
}, },
"web_accessible_resources": [
{
"resources": [
"speech.html",
"speech.js"
],
"matches": ["<all_urls>"]
}
],
"background": { "background": {
"service_worker": "background.js" "service_worker": "background.js"
}, },

View File

@@ -318,24 +318,55 @@ function initDictation() {
if (isRecording) { if (isRecording) {
stopRecording(true); stopRecording(true);
} else { } else {
isRecording = true; // First, get the active tab
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)'; chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
micBtn.innerHTML = '🔴'; const activeTab = tabs[0];
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
finalTranscript = '';
interimTranscript = '';
resultArea.value = '';
copyBtn.style.display = 'none';
chrome.runtime.sendMessage({ // Cannot inject into chrome:// or chrome-extension:// pages
type: 'START_RECORDING_FROM_POPUP', if (!activeTab || !activeTab.url || activeTab.url.startsWith('chrome://') || activeTab.url.startsWith('chrome-extension://') || activeTab.url.startsWith('edge://')) {
payload: { language: 'ar-SA' } statusEl.textContent = '❌ يرجى فتح موقع عادي أولاً (مثل جوجل أو لينكدإن)';
}, (response) => { return;
if (!response || !response.success) {
console.error('Failed to start recording', response);
statusEl.textContent = '❌ فشل بدء التسجيل';
stopRecording(false);
} }
isRecording = true;
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)';
micBtn.innerHTML = '🔴';
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
finalTranscript = '';
interimTranscript = '';
resultArea.value = '';
copyBtn.style.display = 'none';
const extensionUrl = chrome.runtime.getURL('speech.html');
chrome.scripting.executeScript({
target: { tabId: activeTab.id },
func: (url) => {
let iframe = document.getElementById('lja-dictation-frame');
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = 'lja-dictation-frame';
iframe.src = url;
iframe.style.display = 'none';
// Allow microphone explicitly if needed
iframe.setAttribute('allow', 'microphone');
document.body.appendChild(iframe);
}
},
args: [extensionUrl]
}).then(() => {
// Small delay to ensure iframe has loaded and registered listener
setTimeout(() => {
chrome.runtime.sendMessage({
type: 'START_RECORDING_FROM_POPUP',
payload: { language: 'ar-SA' }
});
}, 500);
}).catch(err => {
console.error('Failed to inject iframe:', err);
statusEl.textContent = '❌ فشل الاتصال بالصفحة الحالية';
stopRecording(false);
});
}); });
} }
}); });

10
speech.html Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Speech Recognition Frame</title>
</head>
<body>
<script src="speech.js"></script>
</body>
</html>

117
speech.js Normal file
View File

@@ -0,0 +1,117 @@
// speech.js - Handles webkitSpeechRecognition inside the injected 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;
}
}
chrome.runtime.sendMessage({
type: 'OFFSCREEN_RECORDING_RESULT',
payload: { interimText, finalText }
});
};
recognition.onerror = (event) => {
console.error('[Speech Iframe] Recognition error:', event.error);
if (event.error !== 'no-speech') {
isRecording = false;
stopMediaTracks();
}
chrome.runtime.sendMessage({
type: 'OFFSCREEN_RECORDING_ERROR',
payload: { error: event.error }
});
};
recognition.onend = () => {
if (isRecording) {
try {
recognition.start();
} catch (e) {
console.warn('[Speech Iframe] Restart failed:', e);
}
} else {
stopMediaTracks();
chrome.runtime.sendMessage({ type: 'OFFSCREEN_RECORDING_END' });
}
};
return recognition;
}
// Listen for messages broadcasted across the extension
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'START_RECORDING_FROM_POPUP') {
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;
// Tell popup it started successfully
chrome.runtime.sendMessage({ type: 'OFFSCREEN_RECORDING_START_SUCCESS' });
}
} catch (e) {
console.error('[Speech Iframe] Failed to start:', e);
chrome.runtime.sendMessage({ type: 'OFFSCREEN_RECORDING_ERROR', payload: { error: e.message } });
}
})
.catch((err) => {
console.error('[Speech Iframe] getUserMedia failed:', err);
chrome.runtime.sendMessage({ type: 'OFFSCREEN_RECORDING_ERROR', payload: { error: 'not-allowed' } });
});
}
if (message.type === 'STOP_RECORDING_FROM_POPUP') {
if (isRecording && recognition) {
isRecording = false;
try {
recognition.stop();
} catch (e) {
console.warn('[Speech Iframe] Stop failed:', e);
}
}
stopMediaTracks();
}
});