Skip to content

Standalone JavaScript Integration

Learn how to use Sibilance with vanilla JavaScript without any framework.

Overview

Sibilance works perfectly with vanilla JavaScript, providing a simple API for voice surveys in any web application.

Installation

Via NPM

bash
npm install @sibilance.is/client
javascript
import { SibilanceClient } from '@sibilance.is/client';

Via CDN

html
<script type="module">
  import { SibilanceClient } from 'https://unpkg.com/@sibilance.is/client/dist/index.js';
</script>

Quick Start

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Voice Survey Demo</title>
</head>
<body>
  <h1>Voice Survey</h1>
  
  <button id="start-survey">Start Survey</button>
  <button id="stop-survey" style="display: none;">Stop Survey</button>
  <div id="status"></div>
  <div id="results"></div>

  <script type="module">
    import { SibilanceClient } from '@sibilance.is/client';

    // Create Sibilance client
    const client = new SibilanceClient({
      surveyKey: 'sibilance_your_key_here',
    }, {
      onComplete: (yaml) => {
        console.log('Survey completed!', yaml);
        document.getElementById('results').innerHTML = 
          '<pre>' + JSON.stringify(yaml, null, 2) + '</pre>';
      }
    });

    // UI elements
    const startBtn = document.getElementById('start-survey');
    const stopBtn = document.getElementById('stop-survey');
    const status = document.getElementById('status');

    // Event handlers
    startBtn.addEventListener('click', async () => {
      try {
        await client.connect();
        startBtn.style.display = 'none';
        stopBtn.style.display = 'inline-block';
        status.textContent = 'Survey started! Speak to interact.';
      } catch (error) {
        status.textContent = 'Failed to start: ' + error.message;
      }
    });

    stopBtn.addEventListener('click', async () => {
      await client.disconnect();
      startBtn.style.display = 'inline-block';
      stopBtn.style.display = 'none';
      status.textContent = 'Survey stopped.';
    });

    // Subscribe to state changes
    client.onStateChange((state) => {
      if (state.isConnected) {
        status.textContent = 'Connected';
        
        if (state.isUserSpeaking) {
          status.textContent += ' - Listening...';
        } else if (state.isAIThinking) {
          status.textContent += ' - Thinking...';
        } else if (state.isAISpeaking) {
          status.textContent += ' - Speaking...';
        }
      } else {
        status.textContent = 'Disconnected';
      }
    });
  </script>
</body>
</html>

Complete Example

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Voice Survey</title>
  <style>
    body {
      font-family: system-ui, -apple-system, sans-serif;
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    
    .survey-controls {
      position: fixed;
      bottom: 20px;
      right: 20px;
      display: flex;
      gap: 10px;
    }
    
    .survey-button {
      padding: 15px 30px;
      font-size: 16px;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      background: #007bff;
      color: white;
    }
    
    .survey-button:hover {
      background: #0056b3;
    }
    
    .survey-button.stop {
      background: #dc3545;
    }
    
    .survey-status {
      position: fixed;
      top: 20px;
      right: 20px;
      padding: 10px 20px;
      background: #f8f9fa;
      border-radius: 8px;
      border: 1px solid #dee2e6;
    }
    
    .survey-status.connected {
      background: #d4edda;
      border-color: #c3e6cb;
    }
    
    .survey-progress {
      margin-top: 40px;
      padding: 20px;
      background: #f8f9fa;
      border-radius: 8px;
    }
    
    .collected-info {
      margin-top: 20px;
    }
    
    .info-item {
      padding: 10px;
      margin: 5px 0;
      background: white;
      border-radius: 4px;
      border-left: 3px solid #007bff;
    }
  </style>
</head>
<body>
  <h1>Voice Survey Demo</h1>
  
  <div class="survey-status" id="status">
    Disconnected
  </div>
  
  <div class="survey-controls">
    <button class="survey-button" id="start-survey">🎤 Start Survey</button>
    <button class="survey-button stop" id="stop-survey" style="display: none;">⏹️ Stop Survey</button>
  </div>
  
  <div class="survey-progress" id="progress" style="display: none;">
    <h3>Survey Progress</h3>
    <p id="current-step">Current step: -</p>
    <p id="collected-count">Collected: 0 items</p>
  </div>
  
  <div class="collected-info" id="collected-info">
    <h3>Collected Information</h3>
    <div id="info-list"></div>
  </div>

  <script type="module">
    import { SibilanceClient } from '@sibilance.is/client';

    // Create client
    const client = new SibilanceClient({
      surveyKey: 'sibilance_your_key_here',
    }, {
      onComplete: async (yaml) => {
        console.log('Survey completed!', yaml);
        
        // Show results
        document.getElementById('collected-info').innerHTML = `
          <h3>Survey Completed!</h3>
          <pre>${JSON.stringify(yaml, null, 2)}</pre>
        `;
        
        // Submit to backend
        try {
          const response = await fetch('/api/survey-results', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ results: yaml })
          });
          
          if (response.ok) {
            console.log('Results submitted successfully!');
          }
        } catch (error) {
          console.error('Failed to submit results:', error);
        }
      },
      onRecordInformation: (info) => {
        console.log('Recorded:', info.field, '=', info.value);
        updateCollectedInfo();
      },
      onError: (error) => {
        console.error('Survey error:', error);
        document.getElementById('status').textContent = 'Error: ' + error.message;
      },
      onStepChange: (stepId) => {
        console.log('Step changed to:', stepId);
        document.getElementById('current-step').textContent = `Current step: ${stepId}`;
      }
    });

    // UI elements
    const startBtn = document.getElementById('start-survey');
    const stopBtn = document.getElementById('stop-survey');
    const status = document.getElementById('status');
    const progress = document.getElementById('progress');

    // Update collected info display
    function updateCollectedInfo() {
      const surveyState = client.getSurveyState();
      const infoList = document.getElementById('info-list');
      
      if (surveyState.collectedInfo.length === 0) {
        infoList.innerHTML = '<p>No information collected yet.</p>';
        return;
      }
      
      infoList.innerHTML = surveyState.collectedInfo.map(info => `
        <div class="info-item">
          <strong>${info.field}:</strong> ${info.value}
          ${info.sourceStep ? `<small> (from ${info.sourceStep})</small>` : ''}
        </div>
      `).join('');
      
      document.getElementById('collected-count').textContent = 
        `Collected: ${surveyState.collectedInfo.length} items`;
    }

    // Event handlers
    startBtn.addEventListener('click', async () => {
      try {
        await client.connect();
        startBtn.style.display = 'none';
        stopBtn.style.display = 'inline-block';
        progress.style.display = 'block';
        status.textContent = 'Connected';
        status.classList.add('connected');
      } catch (error) {
        console.error('Failed to start session:', error);
        status.textContent = 'Failed to start: ' + error.message;
      }
    });

    stopBtn.addEventListener('click', async () => {
      await client.disconnect();
      startBtn.style.display = 'inline-block';
      stopBtn.style.display = 'none';
      status.textContent = 'Disconnected';
      status.classList.remove('connected');
    });

    // Subscribe to state changes
    client.onStateChange((state) => {
      if (state.isConnected) {
        status.textContent = 'Connected';
        status.classList.add('connected');
        
        if (state.isUserSpeaking) {
          status.textContent = '🎤 Listening...';
        } else if (state.isAIThinking) {
          status.textContent = '🤔 Thinking...';
        } else if (state.isAISpeaking) {
          status.textContent = '🔊 Speaking...';
        }
        
        // Update progress
        const surveyState = client.getSurveyState();
        if (surveyState.isActive) {
          document.getElementById('current-step').textContent = 
            `Current step: ${surveyState.currentStepId || 'Unknown'}`;
          updateCollectedInfo();
        }
      } else {
        status.textContent = 'Disconnected';
        status.classList.remove('connected');
      }
    });

    // Initial update
    updateCollectedInfo();
  </script>
</body>
</html>

Survey State Management

Access Survey State

javascript
// Get current survey state
const surveyState = client.getSurveyState();

console.log('Is active:', surveyState.isActive);
console.log('Current step:', surveyState.currentStepId);
console.log('Collected info:', surveyState.collectedInfo);
console.log('Conversation log:', surveyState.conversationLog);
console.log('Is complete:', surveyState.isComplete);

Track Collected Information

javascript
const client = new SibilanceClient({
  surveyKey: 'sibilance_your_key_here'
}, {
  onRecordInformation: (info) => {
    console.log(`Recorded ${info.field}: ${info.value}`);
    
    // Update UI
    const infoList = document.getElementById('collected-info');
    const item = document.createElement('div');
    item.textContent = `${info.field}: ${info.value}`;
    infoList.appendChild(item);
  }
});

Display Conversation Log

javascript
function displayConversation() {
  const surveyState = client.getSurveyState();
  const logContainer = document.getElementById('conversation-log');
  
  logContainer.innerHTML = surveyState.conversationLog.map(entry => `
    <div class="log-entry ${entry.speaker}">
      <strong>${entry.speaker === 'ai' ? 'AI' : 'User'}:</strong>
      <p>${entry.message}</p>
      <small>${new Date(entry.timestamp).toLocaleTimeString()}</small>
    </div>
  `).join('');
}

// Update on new entries
const client = new SibilanceClient({
  surveyKey: 'sibilance_your_key_here'
}, {
  onLogConversation: (speaker, message) => {
    displayConversation();
  }
});

Error Handling

javascript
try {
  await client.connect();
} catch (error) {
  console.error('Failed to start session:', error);
  
  // Show user-friendly error
  if (error.message.includes('microphone')) {
    alert('Please allow microphone access to use voice surveys.');
  } else if (error.message.includes('survey')) {
    alert('Survey not found. Please check your Survey Key.');
  } else {
    alert('Failed to start survey. Please try again.');
  }
}

// Or use onError callback
const client = new SibilanceClient({
  surveyKey: 'sibilance_your_key_here'
}, {
  onError: (error) => {
    console.error('Survey error:', error);
    showErrorNotification(error.message);
  }
});

Best Practices

  1. Cleanup - Always stop session and unsubscribe on page unload
  2. Error Handling - Handle errors gracefully with user-friendly messages
  3. State Management - Subscribe to state changes for UI updates
  4. Results Submission - Submit survey results to your backend
  5. Permissions - Request microphone permissions early
  6. Progressive Enhancement - Ensure app works without voice

Browser Support

Sibilance requires:

  • Modern browser with Web Audio API
  • Microphone access
  • HTTPS (required for microphone)

Check for support:

javascript
if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {
  // Sibilance is supported
} else {
  console.warn('Voice features not supported in this browser');
}