mesop-gemini-live-demo / gemini_live.js
Richard
Initial commit
16a84ba
import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
import {GeminiLiveApi} from './gemini_live_api.js';
class GeminiLive extends LitElement {
static properties = {
api_config: {type: String},
enabled: {type: Boolean},
endpoint: {type: String},
startEvent: {type: String},
stopEvent: {type: String},
input_prompt: {type: String},
toolCallEvent: {type: String},
tool_call_responses: {type: String},
};
constructor() {
super();
this.onAudioInputReceived = (e) => {
if (!this.api) {
return;
}
this.api.sendAudioChunk(e.detail.data);
};
this.onVideoInputReceived = (e) => {
if (!this.api) {
return;
}
this.sendVideoChunk(e.detail.data);
};
}
connectedCallback() {
super.connectedCallback();
window.addEventListener('audio-input-received', this.onAudioInputReceived);
window.addEventListener('video-input-received', this.onVideoInputReceived);
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener(
'audio-input-received',
this.onAudioInputReceived,
);
window.removeEventListener(
'video-input-received',
this.onVideoInputReceived,
);
if (this.api) {
this.api.ws.close();
}
}
firstUpdated() {
if (this.enabled) {
this.setupWebSocket();
}
}
setupWebSocket() {
if (this.api) {
return;
}
this.api = new GeminiLiveApi(
this.endpoint,
true,
JSON.parse(this.api_config),
);
this.api.onSetupComplete = () => {
console.log('Setup complete...');
};
this.api.onAudioData = (base64Data) => {
this.dispatchEvent(
new CustomEvent('audio-output-received', {
detail: {data: base64Data},
// Allow event to cross shadow DOM boundaries (both need to be true)
bubbles: true,
composed: true,
}),
);
};
this.api.onClose = () => {
console.log('Web socket closed...');
};
this.api.onToolCall = (toolCalls) => {
this.dispatchEvent(
new MesopEvent(this.toolCallEvent, {
toolCalls: JSON.stringify(toolCalls.functionCalls),
}),
);
};
}
updated(changedProperties) {
if (
changedProperties.has('tool_call_responses') &&
this.tool_call_responses.length > 0
) {
this.api.sendToolResponse(JSON.parse(this.tool_call_responses));
}
if (changedProperties.has('input_prompt') && this.input_prompt.length > 0) {
this.sendTextMessage(this.input_prompt);
}
}
start() {
if (!this.enabled) {
this.dispatchEvent(new MesopEvent(this.startEvent, {}));
this.dispatchEvent(
new CustomEvent('gemini-live-api-started', {
detail: {},
// Allow event to cross shadow DOM boundaries (both need to be true)
bubbles: true,
composed: true,
}),
);
}
this.setupWebSocket();
}
stop() {
this.dispatchEvent(new MesopEvent(this.stopEvent, {}));
this.dispatchEvent(
new CustomEvent('gemini-live-api-stopped', {
detail: {},
// Allow event to cross shadow DOM boundaries (both need to be true)
bubbles: true,
composed: true,
}),
);
if (this.api.ws) {
this.api.ws.close();
}
}
sendTextMessage(text) {
this.api.sendMessage({
client_content: {
turn_complete: true,
turns: [{role: 'user', parts: [{text: text}]}],
},
});
}
sendVideoChunk(base64Image) {
this.api.sendMessage({
realtimeInput: {
mediaChunks: [
{
mime_type: 'image/jpeg',
data: base64Image,
},
],
},
});
}
render() {
if (this.enabled) {
return html`<span @click="${this.stop}"><slot></slot></span>`;
}
return html`<span @click="${this.start}"><slot></slot></span>`;
}
}
customElements.define('gemini-live', GeminiLive);