Skip to main content

Real-time Transcript Updates

Overview

During an AI Assistant conversation, the SDK provides real-time transcript updates that include both the caller’s speech and the AI Assistant’s responses. This allows you to display a live conversation transcript in your application.

Setting Up Transcript Updates

To receive transcript updates, set up the onTranscriptUpdate callback on your TelnyxClient instance:
_telnyxClient.onTranscriptUpdate = (List<TranscriptItem> transcript) {
  // Handle the updated transcript
  for (var item in transcript) {
    print('${item.role}: ${item.content}');
    // item.role will be either 'user' or 'assistant'
    // item.content contains the spoken text
    // item.timestamp contains when the message was received
  }
  
  // Update your UI to display the conversation
  setState(() {
    _conversationTranscript = transcript;
  });
};

TranscriptItem Properties

The TranscriptItem class contains the following properties:
PropertyTypeDescription
idStringUnique identifier for the transcript item
roleStringEither ‘user’ (for the caller) or ‘assistant’ (for the AI Agent)
contentStringThe transcribed text content
timestampDateTimeWhen the transcript item was created

Manual Transcript Management

Retrieving Current Transcript

You can manually retrieve the current transcript at any time:
List<TranscriptItem> currentTranscript = _telnyxClient.transcript;

// Process the transcript
for (var item in currentTranscript) {
  print('${item.timestamp}: [${item.role}] ${item.content}');
}

Clearing Transcript

To clear the transcript (for example, when starting a new conversation):
_telnyxClient.clearTranscript();

UI Implementation Examples

Simple List View

class TranscriptWidget extends StatefulWidget {
  @override
  _TranscriptWidgetState createState() => _TranscriptWidgetState();
}

class _TranscriptWidgetState extends State<TranscriptWidget> {
  List<TranscriptItem> _transcript = [];

  @override
  void initState() {
    super.initState();
    
    // Set up transcript updates
    _telnyxClient.onTranscriptUpdate = (List<TranscriptItem> transcript) {
      setState(() {
        _transcript = transcript;
      });
    };
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _transcript.length,
      itemBuilder: (context, index) {
        final item = _transcript[index];
        return ListTile(
          leading: Icon(
            item.role == 'user' ? Icons.person : Icons.smart_toy,
            color: item.role == 'user' ? Colors.blue : Colors.green,
          ),
          title: Text(item.content),
          subtitle: Text(
            '${item.role}${item.timestamp.toString()}',
            style: TextStyle(fontSize: 12),
          ),
        );
      },
    );
  }
}

Chat Bubble Style

class ChatTranscriptWidget extends StatefulWidget {
  @override
  _ChatTranscriptWidgetState createState() => _ChatTranscriptWidgetState();
}

class _ChatTranscriptWidgetState extends State<ChatTranscriptWidget> {
  List<TranscriptItem> _transcript = [];
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    
    _telnyxClient.onTranscriptUpdate = (List<TranscriptItem> transcript) {
      setState(() {
        _transcript = transcript;
      });
      
      // Auto-scroll to bottom
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (_scrollController.hasClients) {
          _scrollController.animateTo(
            _scrollController.position.maxScrollExtent,
            duration: Duration(milliseconds: 300),
            curve: Curves.easeOut,
          );
        }
      });
    };
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: _transcript.length,
      itemBuilder: (context, index) {
        final item = _transcript[index];
        final isUser = item.role == 'user';
        
        return Align(
          alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
          child: Container(
            margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
            padding: EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: isUser ? Colors.blue[100] : Colors.grey[200],
              borderRadius: BorderRadius.circular(16),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  item.content,
                  style: TextStyle(fontSize: 16),
                ),
                SizedBox(height: 4),
                Text(
                  DateFormat('HH:mm:ss').format(item.timestamp),
                  style: TextStyle(fontSize: 10, color: Colors.grey[600]),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
}

Real-time Processing

Filtering Transcript Items

_telnyxClient.onTranscriptUpdate = (List<TranscriptItem> transcript) {
  // Filter only user messages
  List<TranscriptItem> userMessages = transcript
      .where((item) => item.role == 'user')
      .toList();
  
  // Filter only assistant messages
  List<TranscriptItem> assistantMessages = transcript
      .where((item) => item.role == 'assistant')
      .toList();
  
  // Process latest message
  if (transcript.isNotEmpty) {
    TranscriptItem latestMessage = transcript.last;
    if (latestMessage.role == 'assistant') {
      // Handle new assistant response
      _handleAssistantResponse(latestMessage);
    }
  }
};

Saving Transcript Data

class TranscriptManager {
  static const String _storageKey = 'conversation_transcripts';
  
  static Future<void> saveTranscript(
    String conversationId,
    List<TranscriptItem> transcript,
  ) async {
    final prefs = await SharedPreferences.getInstance();
    
    // Convert to JSON
    List<Map<String, dynamic>> transcriptJson = transcript
        .map((item) => {
              'id': item.id,
              'role': item.role,
              'content': item.content,
              'timestamp': item.timestamp.toIso8601String(),
            })
        .toList();
    
    // Save to storage
    Map<String, dynamic> allTranscripts = 
        jsonDecode(prefs.getString(_storageKey) ?? '{}');
    allTranscripts[conversationId] = transcriptJson;
    
    await prefs.setString(_storageKey, jsonEncode(allTranscripts));
  }
  
  static Future<List<TranscriptItem>?> loadTranscript(
    String conversationId,
  ) async {
    final prefs = await SharedPreferences.getInstance();
    Map<String, dynamic> allTranscripts = 
        jsonDecode(prefs.getString(_storageKey) ?? '{}');
    
    if (!allTranscripts.containsKey(conversationId)) return null;
    
    List<dynamic> transcriptJson = allTranscripts[conversationId];
    return transcriptJson.map((item) => TranscriptItem(
      id: item['id'],
      role: item['role'],
      content: item['content'],
      timestamp: DateTime.parse(item['timestamp']),
    )).toList();
  }
}

Important Notes

  • AI Assistant Only: Transcript updates are only available during AI Assistant conversations initiated through anonymousLogin
  • Real-time Updates: Transcripts are updated in real-time as the conversation progresses
  • Persistent: The transcript persists throughout the call duration
  • Memory Management: Consider clearing transcripts for long conversations to manage memory
  • Text Messages: Text messages sent via sendConversationMessage also appear in the transcript

Troubleshooting

No Transcript Updates

If you’re not receiving transcript updates:
  1. Ensure you’re using anonymousLogin (not regular authentication)
  2. Verify the onTranscriptUpdate callback is set before starting the call
  3. Check that the AI assistant is properly configured for transcription
  4. Confirm the call is active and connected

Missing Messages

If some messages are missing from the transcript:
  1. Check network connectivity during the call
  2. Ensure the callback isn’t being overridden
  3. Verify the AI assistant is configured to provide transcripts

Next Steps