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 ​