UNPKG

@moveo-ai/web-client

Version:

Client side library to load the moveo chat widget and connect it with your agent

138 lines (123 loc) 7.74 kB
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/><meta name="keywords" content="Moveo.ai, Voice, WebRTC, Preview"/><meta name="description" content="Test your WebRTC Voice Integration"/><meta name="twitter:card" content="summary"/><meta property="og:title" content="Moveo.ai WebRTC Preview"/><meta property="og:type" content="website"/><meta property="og:url" content="https://moveo.ai"/><meta property="og:image" content="https://web-client.moveo.ai/public/og-image.jpg"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:description" content="Test your WebRTC Voice Integration"/><title>Moveo.ai WebRTC Preview</title><script src="https://cdn.tailwindcss.com"></script><script>tailwind.config = { important: '#preview-content', corePlugins: { preflight: false }, theme: { extend: { fontFamily: { sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'], }, colors: { 'moveo-primary': '#1B66D6', 'moveo-secondary': '#EEF2FF', }, }, }, };</script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"/><script defer="defer" src="/webrtc-client.min.js"></script></head><body><div id="preview-content" class="relative min-h-screen font-sans text-slate-900 antialiased"><main class="relative z-10 flex min-h-screen items-start md:items-center"><section class="mx-auto flex w-full max-w-6xl flex-col gap-12 px-6 py-16 pb-32 md:flex-row md:items-start md:px-12 md:pb-16 lg:gap-16"><header class="max-w-2xl space-y-6 text-center md:text-left"><h1 class="text-4xl font-semibold leading-tight sm:text-5xl">Preview and test<br class="hidden sm:block"/>your WebRTC voice integration</h1><p class="text-lg text-slate-600 sm:text-xl">Click the widget button at the bottom right to start a test call and try your AI Agent with native WebRTC voice.</p><div id="status" class="text-sm font-medium"></div></header><div class="flex-1"><div id="transcript-log" class="hidden max-h-[60vh] w-full overflow-y-auto rounded-xl border border-slate-200 bg-white/80 p-4 font-mono text-sm shadow-sm backdrop-blur md:max-h-[70vh]"><h3 class="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-400">Live Transcript</h3><div id="transcript-entries" class="space-y-2 pb-24"></div></div></div></section></main></div><script>localStorage.debug = 'moveo:webrtc:*'; const statusNode = document.getElementById('status'); const transcriptLog = document.getElementById('transcript-log'); const transcriptEntries = document.getElementById('transcript-entries'); const toneColor = { warning: 'text-amber-600', error: 'text-rose-600', }; const setStatus = (message, tone = 'error') => { if (!statusNode) return; statusNode.className = `text-sm font-medium ${toneColor[tone] ?? toneColor.error}`; statusNode.textContent = message; }; const addTranscript = (speaker, text) => { if (!transcriptEntries || !transcriptLog) return; transcriptLog.classList.remove('hidden'); const entry = document.createElement('div'); const label = speaker === 'user' ? '🎤 You' : '🤖 Agent'; const color = speaker === 'user' ? 'text-sky-600' : 'text-emerald-600'; entry.innerHTML = `<span class="font-semibold ${color}">${label}:</span> ${text}`; transcriptEntries.appendChild(entry); transcriptLog.scrollTop = transcriptLog.scrollHeight; }; window.addEventListener('load', async () => { const params = new URLSearchParams(window.location.search); const integrationId = (params.get('integration_id') ?? params.get('integrationId') ?? '').trim(); if (!integrationId) { setStatus('Add "?integration_id=YOUR_ID" to the URL to begin.', 'warning'); return; } const hostParam = (params.get('host') ?? '').trim(); const host = hostParam ? hostParam.replace(/\/+$/, '') : ''; if (!window.MoveoWebRTC?.init) { console.error('MoveoWebRTC UMD build is not available on window.'); setStatus( 'Unable to load the WebRTC client. Check the bundle output.', 'error' ); return; } try { const controller = await window.MoveoWebRTC.init({ integrationId, ...(host ? { host } : {}), }); controller.on?.('error', ({ message }) => { setStatus(`Call error: ${message || 'unexpected error'}`, 'error'); }); // One entry element per request_id for the current call; reset when a // new call starts so a prior call's lines don't carry over. let brainEntries = {}; let lastState = 'idle'; const resetTranscript = () => { if (transcriptEntries) transcriptEntries.innerHTML = ''; brainEntries = {}; if (transcriptLog) transcriptLog.classList.add('hidden'); }; // A fresh call is idle/error -> connecting. A mid-call reconnect goes // through 'reconnecting' (or connected -> connecting on an ICE blip), so // the live transcript survives it. controller.on?.('state-change', ({ data }) => { if (data === 'connecting' && (lastState === 'idle' || lastState === 'error')) { resetTranscript(); } lastState = data; }); controller.on?.('transcript', ({ data }) => { if (data && typeof data === 'object' && 'text' in data) { addTranscript('user', data.text); } }); // brain-response streams in chunks; upsert one line per request_id so // the assembled reply grows in place instead of appending each chunk. controller.on?.('brain-response', ({ data }) => { if (!data || typeof data !== 'object' || !('text' in data)) return; if (!transcriptEntries || !transcriptLog) return; transcriptLog.classList.remove('hidden'); const reqId = ('request_id' in data && data.request_id) || '_'; let entry = brainEntries[reqId]; if (!entry) { entry = document.createElement('div'); transcriptEntries.appendChild(entry); brainEntries[reqId] = entry; } entry.innerHTML = `<span class="font-semibold text-emerald-600">🤖 Agent:</span> ${data.text}`; transcriptLog.scrollTop = transcriptLog.scrollHeight; }); // interrupt marks a barge-in: keep the heard text, gray out the unheard // tail (never delete). controller.on?.('interrupt', ({ data }) => { if (!data || typeof data !== 'object' || !('request_id' in data)) return; const reqId = data.request_id || '_'; const entry = brainEntries[reqId]; if (!entry) return; const heard = ('heardText' in data && data.heardText) || ''; const unheard = ('unheardText' in data && data.unheardText) || ''; entry.innerHTML = `<span class="font-semibold text-emerald-600">🤖 Agent:</span> ${heard}` + (unheard ? ` <span class="text-slate-400 line-through">${unheard}</span>` : ''); transcriptLog.scrollTop = transcriptLog.scrollHeight; }); console.log('✅ MoveoWebRTC widget initialized', controller); } catch (error) { console.error('❌ Failed to initialize MoveoWebRTC widget:', error); setStatus('Failed to initialize the WebRTC widget', 'error'); } });</script></body></html>