Files
2026-07-13 13:30:30 +08:00

243 lines
7.2 KiB
JavaScript

// --- Main Application Logic ---
const statusDiv = document.getElementById("status");
const authSection = document.getElementById("auth-section");
const appSection = document.getElementById("app-section");
const sessionEndSection = document.getElementById("session-end-section");
const restartBtn = document.getElementById("restartBtn");
const micBtn = document.getElementById("micBtn");
const cameraBtn = document.getElementById("cameraBtn");
const screenBtn = document.getElementById("screenBtn");
const disconnectBtn = document.getElementById("disconnectBtn");
const textInput = document.getElementById("textInput");
const sendBtn = document.getElementById("sendBtn");
const videoPreview = document.getElementById("video-preview");
const videoPlaceholder = document.getElementById("video-placeholder");
const connectBtn = document.getElementById("connectBtn");
const chatLog = document.getElementById("chat-log");
let currentGeminiMessageDiv = null;
let currentUserMessageDiv = null;
const mediaHandler = new MediaHandler();
const geminiClient = new GeminiClient({
onOpen: () => {
statusDiv.textContent = "Connected";
statusDiv.className = "status connected";
authSection.classList.add("hidden");
appSection.classList.remove("hidden");
// Send hidden instruction
geminiClient.sendText(
`System: Introduce yourself as a demo of the Gemini Live API.
Suggest playing with features like the native audio for accents, multilingual support,
proactive audio by asking you not to speak until I say something specific,
or the affective audio capabilities by changing the emotion in your voice to
match the tone of the conversation. Keep the intro concise and friendly.`
);
},
onMessage: (event) => {
if (typeof event.data === "string") {
try {
const msg = JSON.parse(event.data);
handleJsonMessage(msg);
} catch (e) {
console.error("Parse error:", e);
}
} else {
mediaHandler.playAudio(event.data);
}
},
onClose: (e) => {
console.log("WS Closed:", e);
statusDiv.textContent = "Disconnected";
statusDiv.className = "status disconnected";
showSessionEnd();
},
onError: (e) => {
console.error("WS Error:", e);
statusDiv.textContent = "Connection Error";
statusDiv.className = "status error";
},
});
function handleJsonMessage(msg) {
if (msg.type === "interrupted") {
mediaHandler.stopAudioPlayback();
currentGeminiMessageDiv = null;
currentUserMessageDiv = null;
} else if (msg.type === "turn_complete") {
currentGeminiMessageDiv = null;
currentUserMessageDiv = null;
} else if (msg.type === "user") {
if (currentUserMessageDiv) {
currentUserMessageDiv.textContent += msg.text;
chatLog.scrollTop = chatLog.scrollHeight;
} else {
currentUserMessageDiv = appendMessage("user", msg.text);
}
} else if (msg.type === "gemini") {
if (currentGeminiMessageDiv) {
currentGeminiMessageDiv.textContent += msg.text;
chatLog.scrollTop = chatLog.scrollHeight;
} else {
currentGeminiMessageDiv = appendMessage("gemini", msg.text);
}
}
}
function appendMessage(type, text) {
const msgDiv = document.createElement("div");
msgDiv.className = `message ${type}`;
msgDiv.textContent = text;
chatLog.appendChild(msgDiv);
chatLog.scrollTop = chatLog.scrollHeight;
return msgDiv;
}
// Connect Button Handler
connectBtn.onclick = async () => {
statusDiv.textContent = "Connecting...";
connectBtn.disabled = true;
try {
// Initialize audio context on user gesture
await mediaHandler.initializeAudio();
geminiClient.connect();
} catch (error) {
console.error("Connection error:", error);
statusDiv.textContent = "Connection Failed: " + error.message;
statusDiv.className = "status error";
connectBtn.disabled = false;
}
};
// UI Controls
disconnectBtn.onclick = () => {
geminiClient.disconnect();
};
micBtn.onclick = async () => {
if (mediaHandler.isRecording) {
mediaHandler.stopAudio();
micBtn.textContent = "Start Mic";
} else {
try {
await mediaHandler.startAudio((data) => {
if (geminiClient.isConnected()) {
geminiClient.send(data);
}
});
micBtn.textContent = "Stop Mic";
} catch (e) {
alert("Could not start audio capture");
}
}
};
cameraBtn.onclick = async () => {
if (cameraBtn.textContent === "Stop Camera") {
mediaHandler.stopVideo(videoPreview);
cameraBtn.textContent = "Start Camera";
screenBtn.textContent = "Share Screen";
videoPlaceholder.classList.remove("hidden");
} else {
// If another stream is active (e.g. Screen), stop it first
if (mediaHandler.videoStream) {
mediaHandler.stopVideo(videoPreview);
screenBtn.textContent = "Share Screen";
}
try {
await mediaHandler.startVideo(videoPreview, (base64Data) => {
if (geminiClient.isConnected()) {
geminiClient.sendImage(base64Data);
}
});
cameraBtn.textContent = "Stop Camera";
screenBtn.textContent = "Share Screen";
videoPlaceholder.classList.add("hidden");
} catch (e) {
alert("Could not access camera");
}
}
};
screenBtn.onclick = async () => {
if (screenBtn.textContent === "Stop Sharing") {
mediaHandler.stopVideo(videoPreview);
screenBtn.textContent = "Share Screen";
cameraBtn.textContent = "Start Camera";
videoPlaceholder.classList.remove("hidden");
} else {
// If another stream is active (e.g. Camera), stop it first
if (mediaHandler.videoStream) {
mediaHandler.stopVideo(videoPreview);
cameraBtn.textContent = "Start Camera";
}
try {
await mediaHandler.startScreen(
videoPreview,
(base64Data) => {
if (geminiClient.isConnected()) {
geminiClient.sendImage(base64Data);
}
},
() => {
// onEnded callback (e.g. user stopped sharing from browser)
screenBtn.textContent = "Share Screen";
videoPlaceholder.classList.remove("hidden");
}
);
screenBtn.textContent = "Stop Sharing";
cameraBtn.textContent = "Start Camera";
videoPlaceholder.classList.add("hidden");
} catch (e) {
alert("Could not share screen");
}
}
};
sendBtn.onclick = sendText;
textInput.onkeypress = (e) => {
if (e.key === "Enter") sendText();
};
function sendText() {
const text = textInput.value;
if (text && geminiClient.isConnected()) {
geminiClient.sendText(text);
appendMessage("user", text);
textInput.value = "";
}
}
function resetUI() {
authSection.classList.remove("hidden");
appSection.classList.add("hidden");
sessionEndSection.classList.add("hidden");
mediaHandler.stopAudio();
mediaHandler.stopVideo(videoPreview);
videoPlaceholder.classList.remove("hidden");
micBtn.textContent = "Start Mic";
cameraBtn.textContent = "Start Camera";
screenBtn.textContent = "Share Screen";
chatLog.innerHTML = "";
connectBtn.disabled = false;
}
function showSessionEnd() {
appSection.classList.add("hidden");
sessionEndSection.classList.remove("hidden");
mediaHandler.stopAudio();
mediaHandler.stopVideo(videoPreview);
}
restartBtn.onclick = () => {
resetUI();
};