Fix conversation mic crash on HTTP/IP access with clear insecure-context warning

navigator.mediaDevices is undefined on non-secure origins (HTTP + IP).
Add early guard with red banner in the chat panel, disabled mic button,
and an 8-second toast explaining the two fixes. Also add optional ms
parameter to toast().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-05-28 01:40:27 +02:00
parent 239bf0dad6
commit ed24ff4f8f

View File

@ -409,9 +409,9 @@ function selectVoice(wrap) {
} }
let _toastTimer; let _toastTimer;
function toast(msg, type = '') { function toast(msg, type = '', ms = 3500) {
const el = $('toast'); el.textContent = msg; el.className = 'show ' + type; const el = $('toast'); el.textContent = msg; el.className = 'show ' + type;
clearTimeout(_toastTimer); _toastTimer = setTimeout(() => el.className = '', 3500); clearTimeout(_toastTimer); _toastTimer = setTimeout(() => el.className = '', ms);
} }
function status(msg) { $('status-bar').textContent = msg; } function status(msg) { $('status-bar').textContent = msg; }
function escHtml(s) { function escHtml(s) {
@ -8188,6 +8188,27 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
const turnHistory = $('conv-turn-history'); const turnHistory = $('conv-turn-history');
if (!chatWindow || !micBtn) return; if (!chatWindow || !micBtn) return;
// Warn if microphone API is unavailable (HTTP on non-localhost = insecure context)
if (!navigator.mediaDevices?.getUserMedia) {
if (micStatus) {
micStatus.textContent = 'Mic unavailable — insecure context';
micStatus.style.color = 'var(--red, #e05)';
}
if (micBtn) {
micBtn.disabled = true;
micBtn.title = 'Browser blocks microphone on HTTP. Use http://localhost:7890 or HTTPS.\n' +
'Chrome fix: chrome://flags/#unsafely-treat-insecure-origin-as-secure';
micBtn.style.opacity = '0.4';
}
const warn = document.createElement('div');
warn.style.cssText = 'background:var(--red,#c00);color:#fff;padding:10px 16px;border-radius:8px;margin:12px 0;font-size:13px;line-height:1.5';
warn.innerHTML = '<strong>⚠ Microphone blocked by browser</strong><br>' +
'Browsers only allow microphone access on <b>secure contexts</b> (HTTPS or localhost).<br>' +
'Quick fix: open the app at <b>http://localhost:7890</b> instead of the IP address.<br>' +
'Remote access fix: add the URL in <code>chrome://flags/#unsafely-treat-insecure-origin-as-secure</code>';
chatWindow && chatWindow.parentElement && chatWindow.parentElement.insertBefore(warn, chatWindow);
}
// Restore conv LLM URL from server settings // Restore conv LLM URL from server settings
if (llmUrlInp && _appSettings && _appSettings.conv_llm_url) llmUrlInp.value = _appSettings.conv_llm_url; if (llmUrlInp && _appSettings && _appSettings.conv_llm_url) llmUrlInp.value = _appSettings.conv_llm_url;
if (llmUrlInp) llmUrlInp.addEventListener('input', () => { if (llmUrlInp) llmUrlInp.addEventListener('input', () => {
@ -8367,6 +8388,11 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
async function startRecording() { async function startRecording() {
if (isProcessing) return; if (isProcessing) return;
if (!navigator.mediaDevices?.getUserMedia) {
toast('Microphone unavailable — browser requires a secure context (HTTPS or localhost). ' +
'Access the app via http://localhost:7890 or enable it in chrome://flags/#unsafely-treat-insecure-origin-as-secure', 'error', 8000);
return;
}
let stream; let stream;
try { try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream = await navigator.mediaDevices.getUserMedia({ audio: true });