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"
    />
  );
}