Skip to content

Speaking State Tracking Recipe

Advanced patterns for tracking and responding to voice activity states in Sibilance surveys.

Overview

Sibilance provides real-time state tracking for user speech, AI thinking, and AI speech. This recipe covers patterns for building responsive UIs and logic based on voice activity during surveys.

State Tracking

Basic State Access

typescript
import { useSibilance } from '@sibilance.is/client/react';

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 Thinking: {voiceState.isAIThinking ? 'Yes' : 'No'}</p>
      <p>AI Speaking: {voiceState.isAISpeaking ? 'Yes' : 'No'}</p>
    </div>
  );
}

State Subscription

typescript
import { SibilanceClient } from '@sibilance.is/client';

const client = new SibilanceClient({
  surveyKey: 'sibilance_your_key_here'
});

// Subscribe to state changes
const unsubscribe = client.onStateChange((state) => {
  console.log('State changed:', {
    isConnected: state.isConnected,
    isUserSpeaking: state.isUserSpeaking,
    isAIThinking: state.isAIThinking,
    isAISpeaking: state.isAISpeaking
  });
});

// Cleanup
unsubscribe();

UI Patterns

Visual Indicators

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

  return (
    <div className="voice-indicator">
      {voiceState.isUserSpeaking && (
        <div className="indicator listening">
          <span className="icon">🎤</span>
          <span className="text">Listening...</span>
        </div>
      )}
      
      {voiceState.isAIThinking && (
        <div className="indicator thinking">
          <span className="icon">🤔</span>
          <span className="text">Thinking...</span>
        </div>
      )}
      
      {voiceState.isAISpeaking && (
        <div className="indicator speaking">
          <span className="icon">🔊</span>
          <span className="text">Speaking...</span>
        </div>
      )}
    </div>
  );
}

Animated Indicators

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

  return (
    <div className="voice-indicator-animated">
      {voiceState.isUserSpeaking && (
        <div className="pulse-animation">
          <div className="pulse-ring" />
          <div className="pulse-ring" />
          <div className="pulse-ring" />
        </div>
      )}
    </div>
  );
}
css
.pulse-animation {
  position: relative;
  width: 60px;
  height: 60px;
}

.pulse-ring {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 2px solid #007bff;
  border-radius: 50%;
  animation: pulse 2s infinite;
}

.pulse-ring:nth-child(2) {
  animation-delay: 0.5s;
}

.pulse-ring:nth-child(3) {
  animation-delay: 1s;
}

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

Survey-Specific Patterns

Survey Progress Indicator

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

  return (
    <div className="survey-progress">
      {voiceState.isConnected && (
        <>
          {voiceState.isUserSpeaking && (
            <div className="status listening">
              🎤 Listening to your response...
            </div>
          )}
          
          {voiceState.isAIThinking && (
            <div className="status thinking">
              🤔 Processing your answer...
            </div>
          )}
          
          {voiceState.isAISpeaking && (
            <div className="status speaking">
              🔊 {surveyState.currentStepId ? 'Next question...' : 'Speaking...'}
            </div>
          )}
          
          {!voiceState.isUserSpeaking && 
           !voiceState.isAIThinking && 
           !voiceState.isAISpeaking && (
            <div className="status idle">
              Ready for your response
            </div>
          )}
        </>
      )}
    </div>
  );
}

Conditional UI Based on State

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

  // Disable form inputs while AI is speaking
  const isInputDisabled = voiceState.isAISpeaking || voiceState.isAIThinking;

  return (
    <div>
      <form>
        <input 
          type="text" 
          disabled={isInputDisabled}
          placeholder="Type your answer (or speak)"
        />
        <button disabled={isInputDisabled}>
          Submit
        </button>
      </form>
      
      {voiceState.isUserSpeaking && (
        <div className="recording-indicator">
          Recording your response...
        </div>
      )}
    </div>
  );
}

Advanced Patterns

State-Based Analytics

typescript
const client = new SibilanceClient({
  surveyKey: 'sibilance_your_key_here'
});

let speakingTime = 0;
let listeningTime = 0;
let startTime = Date.now();

client.onStateChange((state) => {
  const now = Date.now();
  const elapsed = now - startTime;
  
  if (state.isUserSpeaking) {
    speakingTime += elapsed;
  } else if (state.isAISpeaking) {
    listeningTime += elapsed;
  }
  
  startTime = now;
});

// Get analytics
function getAnalytics() {
  return {
    totalSpeakingTime: speakingTime,
    totalListeningTime: listeningTime,
    ratio: speakingTime / listeningTime
  };
}

Auto-Pause on User Input

tsx
function AutoPauseSurvey() {
  const { voiceState, pause, resume } = useSibilance({
    surveyKey: 'sibilance_your_key_here'
  });
  const [isTyping, setIsTyping] = useState(false);

  useEffect(() => {
    if (isTyping && voiceState.isAISpeaking) {
      pause();
    } else if (!isTyping && !voiceState.isAISpeaking) {
      resume();
    }
  }, [isTyping, voiceState.isAISpeaking]);

  return (
    <input
      onFocus={() => setIsTyping(true)}
      onBlur={() => setIsTyping(false)}
      placeholder="Type your answer"
    />
  );
}