Skip to main content

Text Chat

Send text messages and handle streaming responses from AI characters.

Sending Messages

Basic Send

// Fire-and-forget (synchronous wrapper)
character.SendText("Hello!");

// Async
await character.SendTextAsync("Hello!");

Text-Only Mode

By default, SendText automatically suppresses TTS when no voice session is active. To explicitly control this:

// Force text-only (no TTS audio generated)
character.SendText("Hello!", textOnly: true);

// Force voice response (even without an active voice session)
character.SendText("Hello!", textOnly: false);

Handling Streaming Responses

Bot responses arrive as a stream of chunks. Each BotResponse event contains a piece of the full reply.

Accumulating Text

private string _fullResponse = "";

void Start()
{
character.OnBotResponse += HandleBotResponse;
}

void HandleBotResponse(BotResponse response)
{
if (response.IsFinal)
{
// Final chunk: response.Text contains the complete response
_fullResponse = response.Text;
Debug.Log($"Complete: {_fullResponse}");
}
else
{
// Partial chunk: accumulate
_fullResponse += response.Text;
}
}

Using CurrentPartialResponse

The EstuaryCharacter component automatically accumulates streaming text:

void Update()
{
// Always shows the latest accumulated text
responseLabel.text = character.CurrentPartialResponse;
}

BotResponse Fields

FieldTypeDescription
TextstringText content of this chunk
IsFinalbooltrue when the response is complete
IsPartialbooltrue for streaming chunks
MessageIdstringUnique ID for this response
ChunkIndexintSequential index of this chunk
IsInterjectionbooltrue if this is a proactive message

Chat UI Example

A simple TextMeshPro-based chat display:

using UnityEngine;
using TMPro;
using Estuary;
using Estuary.Models;

public class ChatUI : MonoBehaviour
{
[SerializeField] private EstuaryCharacter character;
[SerializeField] private TMP_InputField inputField;
[SerializeField] private TMP_Text chatLog;
[SerializeField] private TMP_Text streamingText;

void Start()
{
character.OnBotResponse += OnBotResponse;
character.OnConnected += OnConnected;

inputField.onSubmit.AddListener(OnSubmit);
}

void OnConnected(SessionInfo session)
{
chatLog.text += "\n<color=#888>[Connected]</color>";
}

void OnSubmit(string text)
{
if (string.IsNullOrEmpty(text)) return;

// Display user message
chatLog.text += $"\n<b>You:</b> {text}";

// Send to character
character.SendText(text, textOnly: true);

// Clear input
inputField.text = "";
inputField.ActivateInputField();

// Clear streaming display
streamingText.text = "";
}

void OnBotResponse(BotResponse response)
{
if (response.IsFinal)
{
// Move final response to chat log
chatLog.text += $"\n<b>AI:</b> {response.Text}";
streamingText.text = "";
}
else
{
// Show streaming text
streamingText.text = character.CurrentPartialResponse;
}
}
}

Actions in Text Responses

Bot responses may contain action tags (e.g., <action name="wave" />). By default, EstuaryCharacter strips these before updating CurrentPartialResponse. To access the raw text with action tags, handle the BotResponse event directly -- the Text field contains the original text before stripping.

To disable automatic stripping, uncheck Strip Actions From Text on the EstuaryCharacter component.

See Action System for details on handling actions.


Interjections

Characters can send proactive messages (interjections) without user input. These have IsInterjection = true:

character.OnBotResponse += (response) =>
{
if (response.IsInterjection)
{
// Character is speaking unprompted
ShowNotification(response.Text);
}
};

Next Steps