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, pass a second bool argument:

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

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

Scripted Lines

Use SayLine / SayLineAsync to make the character speak a specific prewritten line verbatim instead of generating a response from the LLM:

// Fire-and-forget
character.SayLine("Welcome to the museum!");

// Async, with optional text-only override
await character.SayLineAsync("Welcome to the museum!", textOnly: false);

When textOnly is false (the default), the server generates TTS audio for the line. When true, the line is delivered as text only.


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, 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 in the EstuaryCharacter Inspector. This option is only configurable in the Inspector; there is no public runtime property.

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