Skip to content

React Integration

Learn how to integrate Sibilance voice surveys into your React applications using the provided React hooks and components.

Overview

Sibilance provides React-specific exports for seamless integration:

  • useSibilance - Hook to access survey state and controls
  • FloatingMicButton - Pre-built floating microphone button component
  • SurveyState - Access to survey progress and collected data

Installation

bash
npm install @sibilance.is/client

Basic Setup

1. Use the Hook

The useSibilance hook provides everything you need:

tsx
import { useSibilance, FloatingMicButton } from '@sibilance.is/client/react';

function VoiceSurvey() {
  const { voiceState, surveyState, toggleSession } = useSibilance({
    surveyKey: process.env.NEXT_PUBLIC_SIBILANCE_SURVEY_KEY!,
  }, {
    onComplete: (yaml) => {
      console.log('Survey completed!', yaml);
      // Submit to your backend
      fetch('/api/survey-results', {
        method: 'POST',
        body: JSON.stringify({ results: yaml })
      });
    }
  });

  return (
    <FloatingMicButton
      isConnected={voiceState.isConnected}
      onClick={toggleSession}
    />
  );
}

Hook API

useSibilance

typescript
const {
  voiceState,      // Voice session state
  surveyState,     // Survey progress state
  toggleSession,   // Start/stop voice session
  complete,        // Manually complete survey
  pause            // Pause survey
} = useSibilance(config, callbacks);

Configuration

typescript
interface SibilanceConfig {
  surveyKey?: string;         // Survey key (production mode)
  survey?: SurveyConfig;      // Direct config (editor/test mode)
  backendUrl?: string;        // Optional: Custom backend URL
  autoStart?: boolean;         // Optional: Auto-start voice session
  customInstructions?: string; // Optional: Additional AI instructions
}

Callbacks

typescript
interface SurveyCallbacks {
  onComplete?: (yaml: any[]) => void;
  onPause?: () => void;
  onRecordInformation?: (info: CollectedInformation) => void;
  onLogConversation?: (speaker: 'ai' | 'user', message: string) => void;
  onError?: (error: Error) => void;
  onStepChange?: (stepId: string) => void;
}

Voice State

Access voice session state:

tsx
function VoiceStatus() {
  const { voiceState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });

  return (
    <div>
      <p>Connected: {voiceState.isConnected ? 'Yes' : 'No'}</p>
      <p>User Speaking: {voiceState.isUserSpeaking ? 'Yes' : 'No'}</p>
      <p>AI Speaking: {voiceState.isAISpeaking ? 'Yes' : 'No'}</p>
      <p>AI Thinking: {voiceState.isAIThinking ? 'Yes' : 'No'}</p>
    </div>
  );
}

Survey State

Track survey progress:

tsx
function SurveyProgress() {
  const { surveyState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });

  return (
    <div>
      {surveyState.isActive && (
        <>
          <p>Survey in progress...</p>
          <p>Current step: {surveyState.currentStepId}</p>
          <p>Collected info: {surveyState.collectedInfo.length} items</p>
          <p>Conversation log: {surveyState.conversationLog.length} entries</p>
        </>
      )}
      {surveyState.isComplete && (
        <p>Survey completed!</p>
      )}
    </div>
  );
}

Components

FloatingMicButton

Pre-built floating microphone button component.

tsx
import { FloatingMicButton } from '@sibilance.is/client/react';

function App() {
  const { voiceState, toggleSession } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });

  return (
    <FloatingMicButton
      isConnected={voiceState.isConnected}
      onClick={toggleSession}
      position="bottom-right"
    />
  );
}

Props:

  • isConnected - Whether voice session is active (required)
  • onClick - Toggle session function (required)
  • position - 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' (default: 'bottom-right')
  • className - Additional CSS classes
  • style - Inline styles

Common Patterns

Auto-Start Survey

tsx
function AutoStartSurvey() {
  const { toggleSession } = useSibilance({
    surveyKey: 'sibilance_your_key_here',
    autoStart: true
  }, {
    onComplete: (yaml) => {
      console.log('Survey completed!', yaml);
    }
  });

  return <YourApp />;
}

Conditional Rendering Based on State

tsx
function SurveyStatus() {
  const { voiceState, surveyState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });

  return (
    <div>
      {voiceState.isConnected && (
        <div className="status-indicator">
          {voiceState.isUserSpeaking && <span>🎤 Listening...</span>}
          {voiceState.isAIThinking && <span>🤔 Thinking...</span>}
          {voiceState.isAISpeaking && <span>🔊 Speaking...</span>}
        </div>
      )}
      
      {surveyState.isActive && (
        <div className="survey-progress">
          <p>Step: {surveyState.currentStepId}</p>
          <p>Collected: {surveyState.collectedInfo.length} items</p>
        </div>
      )}
      
      {surveyState.isComplete && (
        <div className="survey-complete">
          <p>✅ Survey completed!</p>
        </div>
      )}
    </div>
  );
}

Handle Survey Completion

tsx
function SurveyHandler() {
  const { surveyState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  }, {
    onComplete: async (yaml) => {
      // Submit results to your backend
      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!');
      }
    }
  });

  return <YourApp />;
}

Track Collected Information

tsx
function InformationTracker() {
  const { surveyState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  }, {
    onRecordInformation: (info) => {
      console.log('Information recorded:', info);
      // info: { field: string, value: string, sourceStep?: string, timestamp: number }
    }
  });

  return (
    <div>
      <h3>Collected Information</h3>
      <ul>
        {surveyState.collectedInfo.map((info, i) => (
          <li key={i}>
            <strong>{info.field}:</strong> {info.value}
            {info.sourceStep && <span> (from {info.sourceStep})</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

Conversation Log

tsx
function ConversationLog() {
  const { surveyState } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });

  return (
    <div className="conversation-log">
      <h3>Conversation</h3>
      {surveyState.conversationLog.map((entry, i) => (
        <div key={i} className={`entry ${entry.speaker}`}>
          <strong>{entry.speaker === 'ai' ? 'AI' : 'User'}:</strong>
          <p>{entry.message}</p>
          <small>{new Date(entry.timestamp).toLocaleTimeString()}</small>
        </div>
      ))}
    </div>
  );
}

Complete Example

tsx
// App.tsx
import { useSibilance, FloatingMicButton } from '@sibilance.is/client/react';
import { useState, useEffect } from 'react';

function App() {
  const [results, setResults] = useState<any[]>([]);
  
  const { voiceState, surveyState, toggleSession } = useSibilance({
    surveyKey: process.env.NEXT_PUBLIC_SIBILANCE_SURVEY_KEY!,
  }, {
    onComplete: (yaml) => {
      console.log('Survey completed!', yaml);
      setResults(yaml);
      
      // Submit to backend
      fetch('/api/survey-results', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ results: yaml })
      });
    },
    onRecordInformation: (info) => {
      console.log('Recorded:', info.field, '=', info.value);
    },
    onError: (error) => {
      console.error('Survey error:', error);
    }
  });

  return (
    <div className="app">
      <header>
        <h1>My Website</h1>
        {voiceState.isConnected && (
          <div className="voice-status">
            {voiceState.isUserSpeaking && '🎤 Listening...'}
            {voiceState.isAIThinking && '🤔 Thinking...'}
            {voiceState.isAISpeaking && '🔊 Speaking...'}
          </div>
        )}
      </header>
      
      <main>
        {surveyState.isActive && (
          <div className="survey-progress">
            <p>Survey in progress...</p>
            <p>Current step: {surveyState.currentStepId}</p>
            <p>Collected: {surveyState.collectedInfo.length} items</p>
          </div>
        )}
        
        {surveyState.isComplete && (
          <div className="survey-complete">
            <h2>Survey Completed!</h2>
            <pre>{JSON.stringify(results, null, 2)}</pre>
          </div>
        )}
        
        <YourContent />
      </main>

      {/* Floating microphone button */}
      <FloatingMicButton
        isConnected={voiceState.isConnected}
        onClick={toggleSession}
        position="bottom-right"
      />
    </div>
  );
}

export default App;

TypeScript Support

Full TypeScript support with type definitions:

tsx
import type { 
  SibilanceConfig,
  SurveyCallbacks,
  VoiceSessionState,
  SurveyState
} from '@sibilance.is/client/react';

// Typed component
const MyComponent: React.FC = () => {
  const config: SibilanceConfig = {
    surveyKey: 'sibilance_your_key_here'
  };
  
  const callbacks: SurveyCallbacks = {
    onComplete: (yaml) => {
      console.log('Completed:', yaml);
    }
  };
  
  const { voiceState, surveyState } = useSibilance(config, callbacks);
  
  return <div>{voiceState.isConnected && 'Connected'}</div>;
};

Styling

Custom Styles

tsx
<FloatingMicButton
  isConnected={voiceState.isConnected}
  onClick={toggleSession}
  style={{
    '--sibilance-primary-color': '#007bff',
    '--sibilance-background': '#ffffff',
    '--sibilance-border-radius': '50%'
  } as React.CSSProperties}
/>

CSS Classes

tsx
<FloatingMicButton
  isConnected={voiceState.isConnected}
  onClick={toggleSession}
  className="my-custom-button"
/>
css
.my-custom-button {
  /* Your custom styles */
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}