Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 34 additions & 29 deletions chat-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,17 @@ document.addEventListener("DOMContentLoaded", () => {
}
}

function updateVoiceToggleUI() {
if (voiceToggleBtn) {
voiceToggleBtn.textContent = autoSpeakEnabled ? "🔊 Voice On" : "🔇 Voice Off";
voiceToggleBtn.style.backgroundColor = autoSpeakEnabled ? "#4CAF50" : "";
}
}
function updateVoiceToggleUI() {
if (voiceToggleBtn) {
if (autoSpeakEnabled) {
voiceToggleBtn.innerHTML = '<i class="fas fa-volume-up"></i> AI Voice On';
voiceToggleBtn.classList.add("active");
} else {
voiceToggleBtn.innerHTML = '<i class="fas fa-volume-mute"></i> AI Voice Off';
voiceToggleBtn.classList.remove("active");
}
}
}

function speakMessage(text, onEnd = null) {
if (!synth || !window.SpeechSynthesisUtterance) {
Expand Down Expand Up @@ -589,13 +594,13 @@ document.addEventListener("DOMContentLoaded", () => {
window._chatInternals.recognition = recognition;
}

recognition.onstart = () => {
isListening = true;
if (voiceInputBtn) {
voiceInputBtn.classList.add("listening");
voiceInputBtn.innerHTML = '<i class="fas fa-microphone-slash"></i>';
}
};
recognition.onstart = () => {
isListening = true;
if (voiceInputBtn) {
voiceInputBtn.classList.add("active");
voiceInputBtn.innerHTML = '<i class="fas fa-headset"></i> Voice Chat On';
}
};

recognition.onresult = (event) => {
let finalTranscript = "";
Expand Down Expand Up @@ -624,22 +629,22 @@ document.addEventListener("DOMContentLoaded", () => {
}
};

recognition.onerror = (event) => {
isListening = false;
if (voiceInputBtn) {
voiceInputBtn.classList.remove("listening");
voiceInputBtn.innerHTML = '<i class="fas fa-microphone"></i>';
}
console.error("Speech recognition error:", event.error);
};
recognition.onend = () => {
isListening = false;
if (voiceInputBtn) {
voiceInputBtn.classList.remove("listening");
voiceInputBtn.innerHTML = '<i class="fas fa-microphone"></i>';
}
};
recognition.onerror = (event) => {
isListening = false;
if (voiceInputBtn) {
voiceInputBtn.classList.remove("active");
voiceInputBtn.innerHTML = '<i class="fas fa-headset"></i> Voice Chat Off';
}
console.error("Speech recognition error:", event.error);
};

recognition.onend = () => {
isListening = false;
if (voiceInputBtn) {
voiceInputBtn.classList.remove("active");
voiceInputBtn.innerHTML = '<i class="fas fa-headset"></i> Voice Chat Off';
}
};

return true;
} catch (error) {
Expand Down
246 changes: 29 additions & 217 deletions chat-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,35 +509,20 @@ document.addEventListener("DOMContentLoaded", () => {
document.getElementById("model-select").focus();
});
};
checkFirstLaunch();
const setupVoiceInputButton = () => {
if (!("webkitSpeechRecognition" in window || "SpeechRecognition" in window)) {
const voiceInputBtn = document.getElementById("voice-input-btn");
if (voiceInputBtn) {
voiceInputBtn.disabled = true;
voiceInputBtn.title = "Voice input not supported in this browser";
}
return;
}
const inputButtonsContainer = document.querySelector(".input-buttons-container");
if (!window._chatInternals.voiceInputBtn && inputButtonsContainer) {
const voiceInputBtn = document.createElement("button");
voiceInputBtn.id = "voice-input-btn";
voiceInputBtn.innerHTML = '<i class="fas fa-microphone"></i>';
voiceInputBtn.title = "Voice input";
inputButtonsContainer.insertBefore(voiceInputBtn, document.getElementById("send-button"));
window._chatInternals.setVoiceInputButton(voiceInputBtn);
voiceInputBtn.addEventListener("click", toggleSpeechRecognition);
}
};
setupVoiceInputButton();
if ("webkitSpeechRecognition" in window || "SpeechRecognition" in window) {
try {
toggleSpeechRecognition();
} catch (err) {
console.error("Automatic speech recognition start failed:", err);
}
}
checkFirstLaunch();
const setupVoiceChatToggle = () => {
const btn = document.getElementById("voice-chat-toggle");
if (!btn) return;
if (!("webkitSpeechRecognition" in window || "SpeechRecognition" in window)) {
btn.disabled = true;
btn.title = "Voice input not supported in this browser";
return;
}
btn.title = "Toggle voice input";
window._chatInternals.setVoiceInputButton(btn);
btn.addEventListener("click", toggleSpeechRecognition);
};
setupVoiceChatToggle();
document.addEventListener("click", e => {
if (e.target.closest(".image-button-container")) {
e.preventDefault();
Expand Down Expand Up @@ -579,184 +564,11 @@ document.addEventListener("DOMContentLoaded", () => {
if (initialSession.messages?.length > 0) renderStoredMessages(initialSession.messages);
chatInput.disabled = false;
chatInput.focus();
const voiceChatModal = document.getElementById("voice-chat-modal");
const openVoiceChatModalBtn = document.getElementById("open-voice-chat-modal");
const closeVoiceChatModalBtn = document.getElementById("voice-chat-modal-close");
const voiceSettingsModal = document.getElementById("voice-settings-modal");
const openVoiceSettingsModalBtn = document.getElementById("open-voice-settings-modal");
const voiceChatImage = document.getElementById("voice-chat-image");
let slideshowInterval = null;
const startVoiceChatSlideshow = () => {
if (slideshowInterval) clearInterval(slideshowInterval);
const currentSession = Storage.getCurrentSession();
let lastMessage = currentSession.messages.slice(-1)[0]?.content || "default scene";
let imagePrompt = "";
for (const { pattern, group } of imagePatterns) {
const match = lastMessage.match(pattern);
if (match) {
imagePrompt = match[group].trim();
break;
}
}
if (!imagePrompt) {
imagePrompt = lastMessage.replace(/image|picture|show me|generate/gi, "").trim();
}
imagePrompt = imagePrompt.slice(0, 100) + ", photographic";
const updateImage = () => {
const seed = randomSeed();
voiceChatImage.src = `https://image.pollinations.ai/prompt/${encodeURIComponent(imagePrompt)}?width=512&height=512&seed=${seed}&nolog=true&referrer=unityailab.com`;
};
updateImage();
slideshowInterval = setInterval(updateImage, 10000);
};
const stopVoiceChatSlideshow = () => {
if (slideshowInterval) {
clearInterval(slideshowInterval);
slideshowInterval = null;
}
};
let voiceBuffer = "";
let silenceTimeout = null;
const setupCustomSpeechRecognition = () => {
if (!window._chatInternals.recognition) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
showToast("Speech recognition not supported in this browser");
return false;
}
window._chatInternals.recognition = new SpeechRecognition();
const recognition = window._chatInternals.recognition;
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = "en-US";
recognition.onstart = () => {
window._chatInternals.isListening = true;
showToast("Voice recognition active");
document.getElementById("voice-chat-start").disabled = true;
document.getElementById("voice-chat-stop").disabled = false;
};
recognition.onend = () => {
window._chatInternals.isListening = false;
document.getElementById("voice-chat-start").disabled = false;
document.getElementById("voice-chat-stop").disabled = true;
};
recognition.onerror = event => {
window._chatInternals.isListening = false;
document.getElementById("voice-chat-start").disabled = false;
document.getElementById("voice-chat-stop").disabled = true;
const errors = {
"no-speech": "No speech detected. Please try again.",
"not-allowed": "Microphone access denied. Please allow microphone access in your browser settings.",
"service-not-allowed": "Microphone access denied. Please allow microphone access in your browser settings.",
};
showToast(errors[event.error] || "Voice recognition error: " + event.error);
};
recognition.onresult = event => {
let interimTranscript = "";
let finalTranscript = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
const processed = transcript.trim();
if (!handleVoiceCommand(processed)) finalTranscript += processed + " ";
} else {
interimTranscript += transcript;
}
}
voiceBuffer += finalTranscript;
chatInput.value = voiceBuffer + interimTranscript;
if (finalTranscript) {
clearTimeout(silenceTimeout);
silenceTimeout = setTimeout(() => {
if (voiceBuffer.trim()) {
window.addNewMessage({ role: "user", content: voiceBuffer.trim() });
window.sendToPollinations(startVoiceChatSlideshow);
voiceBuffer = "";
chatInput.value = "";
}
}, 1500);
}
};
}
return true;
};
const setupVoiceChatControls = () => {
const modalBody = voiceChatModal.querySelector(".modal-body");
let voiceSelectChat = modalBody.querySelector("#voice-select-voicechat");
if (!voiceSelectChat) {
const voiceSelectContainer = document.createElement("div");
voiceSelectContainer.className = "form-group mb-3";
const voiceSelectLabel = document.createElement("label");
voiceSelectLabel.className = "form-label";
voiceSelectLabel.innerHTML = '<i class="fas fa-headset"></i> Voice Selection:';
voiceSelectLabel.htmlFor = "voice-select-voicechat";
voiceSelectChat = document.createElement("select");
voiceSelectChat.id = "voice-select-voicechat";
voiceSelectChat.className = "form-control";
voiceSelectContainer.appendChild(voiceSelectLabel);
voiceSelectContainer.appendChild(voiceSelectChat);
const insertAfter = modalBody.querySelector("p") || voiceChatImage;
if (insertAfter?.nextSibling) modalBody.insertBefore(voiceSelectContainer, insertAfter.nextSibling);
else modalBody.appendChild(voiceSelectContainer);
}
const existingControls = modalBody.querySelector(".voice-chat-controls");
if (existingControls) existingControls.remove();
const controlsDiv = document.createElement("div");
controlsDiv.className = "voice-chat-controls";
Object.assign(controlsDiv.style, { display: "flex", gap: "10px", marginTop: "15px" });
const startBtn = document.createElement("button");
startBtn.id = "voice-chat-start";
startBtn.className = "btn btn-primary";
startBtn.textContent = "Start Listening";
startBtn.style.width = "100%";
startBtn.style.padding = "10px";
startBtn.disabled = window._chatInternals.isListening;
const stopBtn = document.createElement("button");
stopBtn.id = "voice-chat-stop";
stopBtn.className = "btn btn-danger";
stopBtn.textContent = "Stop Listening";
stopBtn.style.width = "100%";
stopBtn.style.padding = "10px";
stopBtn.disabled = !window._chatInternals.isListening;
controlsDiv.appendChild(startBtn);
controlsDiv.appendChild(stopBtn);
modalBody.appendChild(controlsDiv);
startBtn.addEventListener("click", () => {
if (!setupCustomSpeechRecognition()) return showToast("Failed to initialize speech recognition");
try {
window._chatInternals.recognition.start();
startVoiceChatSlideshow();
} catch (error) {
showToast("Could not start speech recognition: " + error.message);
}
});
stopBtn.addEventListener("click", () => {
if (window._chatInternals.recognition && window._chatInternals.isListening) {
window._chatInternals.recognition.stop();
stopVoiceChatSlideshow();
showToast("Voice recognition stopped");
}
});
};
const updateAllVoiceDropdowns = selectedIndex => {
["voice-select", "voice-select-modal", "voice-settings-modal", "voice-select-voicechat"].forEach(id => {
const dropdown = document.getElementById(id);
if (dropdown) dropdown.value = selectedIndex;
});
};
openVoiceChatModalBtn.addEventListener("click", () => {
voiceChatModal.classList.remove("hidden");
setupVoiceChatControls();
window._chatInternals.populateAllVoiceDropdowns();
});
closeVoiceChatModalBtn.addEventListener("click", () => {
voiceChatModal.classList.add("hidden");
if (window._chatInternals.recognition && window._chatInternals.isListening) window._chatInternals.recognition.stop();
stopVoiceChatSlideshow();
});
openVoiceSettingsModalBtn.addEventListener("click", () => {
voiceSettingsModal.classList.remove("hidden");
window._chatInternals.populateAllVoiceDropdowns();
const voiceSettingsModal = document.getElementById("voice-settings-modal");
const openVoiceSettingsModalBtn = document.getElementById("open-voice-settings-modal");
openVoiceSettingsModalBtn.addEventListener("click", () => {
voiceSettingsModal.classList.remove("hidden");
window._chatInternals.populateAllVoiceDropdowns();
const voiceSpeedInput = document.getElementById("voice-speed");
const voicePitchInput = document.getElementById("voice-pitch");
const voiceSpeedValue = document.getElementById("voice-speed-value");
Expand All @@ -766,9 +578,9 @@ document.addEventListener("DOMContentLoaded", () => {
voiceSpeedValue.textContent = `${voiceSpeedInput.value}x`;
voicePitchValue.textContent = `${voicePitchInput.value}x`;
});
document.getElementById("voice-settings-modal-close").addEventListener("click", () => voiceSettingsModal.classList.add("hidden"));
document.getElementById("voice-settings-cancel").addEventListener("click", () => voiceSettingsModal.classList.add("hidden"));
document.getElementById("voice-settings-save").addEventListener("click", () => {
document.getElementById("voice-settings-modal-close").addEventListener("click", () => voiceSettingsModal.classList.add("hidden"));
document.getElementById("voice-settings-cancel").addEventListener("click", () => voiceSettingsModal.classList.add("hidden"));
document.getElementById("voice-settings-save").addEventListener("click", () => {
const voiceSpeedInput = document.getElementById("voice-speed");
const voicePitchInput = document.getElementById("voice-pitch");
const voiceSelectModal = document.getElementById("voice-select-modal");
Expand All @@ -780,14 +592,14 @@ document.addEventListener("DOMContentLoaded", () => {
localStorage.setItem("voiceSpeed", voiceSpeed);
localStorage.setItem("voicePitch", voicePitch);
window._chatInternals.updateVoiceToggleUI();
updateAllVoiceDropdowns(selectedVoiceIndex);
window._chatInternals.updateAllVoiceDropdowns(selectedVoiceIndex);
voiceSettingsModal.classList.add("hidden");
showToast("Voice settings saved");
});
document.getElementById("voice-speed").addEventListener("input", () => {
document.getElementById("voice-speed-value").textContent = `${document.getElementById("voice-speed").value}x`;
});
document.getElementById("voice-pitch").addEventListener("input", () => {
document.getElementById("voice-pitch-value").textContent = `${document.getElementById("voice-pitch").value}x`;
});
document.getElementById("voice-speed").addEventListener("input", () => {
document.getElementById("voice-speed-value").textContent = `${document.getElementById("voice-speed").value}x`;
});
document.getElementById("voice-pitch").addEventListener("input", () => {
document.getElementById("voice-pitch-value").textContent = `${document.getElementById("voice-pitch").value}x`;
});
});
Loading