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 during a response​

Character actions arrive as typed client_action events. EstuaryCharacter forwards response text unchanged to CurrentPartialResponse and OnBotResponse; action tags are not part of the current response format.

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​