Skip to main content

Text Chat

Send text messages and render streaming responses from an Estuary character.

Sending a Message

Call Send Text on a character's UEstuaryCharacterComponent. It returns a message_id you can use to correlate the streaming response.

FString MessageId = CharacterComponent->SendText(TEXT("Tell me about the ocean."));

In Blueprint: drag from the Character Component, call Send Text, and store the returned Message Id if you want to correlate or interrupt later.

The message must be non-empty for the server to respond.


Receiving Streaming Responses

Responses stream back in chunks. Bind On Bot Response and accumulate the text until bIsFinal is true.

void AMyActor::BeginPlay()
{
Super::BeginPlay();
CharacterComponent->OnBotResponse.AddDynamic(this, &AMyActor::HandleResponse);
}

void AMyActor::HandleResponse(const FEstuaryBotResponse& Response)
{
// Append each delta chunk to build the full reply
AccumulatedText += Response.Text;

if (Response.bIsFinal)
{
UE_LOG(LogTemp, Log, TEXT("Character said: %s"), *AccumulatedText);
AccumulatedText.Reset();
}
}

Bot response fields

FieldTypeDescription
TextstringThe delta text for this chunk. Concatenate across chunks.
MessageIdstringMatches the message_id returned by SendText.
ChunkIndexint0-based chunk counter (-1 when not provided).
bIsFinalbooltrue on the final chunk — no more chunks for this message follow.
bIsInterjectionbooltrue for a backchannel/interjection rather than a direct answer.

:::tip Streaming UI Update your on-screen text on every chunk for a live "typing" effect, and treat bIsFinal as the cue to commit the message to your chat history and re-enable the input field. :::


Text-Only Mode

By default the server also generates spoken audio (bot_voice). To get text without TTS audio — for a silent chat UI — there are two ways:

  • Say Line with bTextOnly = true (for scripted lines, below).
  • For user turns, the text-only flag lives on the lower-level FEstuaryUserText payload; if you don't attach an audio player component, audio chunks are simply ignored.

Scripted Lines (Say Line)

Say Line makes the character speak a prewritten line via TTS, skipping the LLM entirely. Use it for greetings, tutorials, or any moment you want exact words.

CharacterComponent->SayLine(TEXT("Welcome to the demo!"), /*bTextOnly=*/false);

Differences from SendText:

  • The server auto-interrupts any in-flight response first.
  • The server mints its own message_idSayLine returns nothing. The reply still arrives through OnBotResponse / OnBotVoice with that server-generated ID.
  • The line is saved to conversation history as something the character naturally said.

Set bTextOnly = true to get only the text back with no spoken audio.


Interrupting a Response

Call Interrupt to abort the response that is currently streaming. The component targets its most recent message automatically.

CharacterComponent->Interrupt();

After interrupting:

  • OnBotResponse chunks for the interrupted message stop arriving.
  • The server sends an acknowledgement, firing On Interrupt Acked with a Reason.

Interrupt is a no-op if there is no active turn.

void AMyActor::HandleInterruptAck(const FEstuaryInterruptPayload& Payload)
{
UE_LOG(LogTemp, Log, TEXT("Interrupted: %s"), *Payload.Reason);
}

Updating Preferences

Adjust session-level behavior flags with Update Preferences. Changes apply to subsequent responses in the same session.

FEstuaryPreferences Prefs;
Prefs.bEnableVisionAcknowledgment = true; // bot verbally acknowledges what it sees
CharacterComponent->UpdatePreferences(Prefs);

For preference keys not yet exposed as explicit fields, use the ExtraPreferences map as a forward-compatible key/value bag.


Memory Updates

After a conversation ends, the server may extract long-term memories in the background and push them to the client. Bind On Memory Updated on the subsystem:

Estuary->OnMemoryUpdated.AddDynamic(this, &AMyActor::HandleMemory);

void AMyActor::HandleMemory(const FEstuaryMemoryUpdated& Update)
{
UE_LOG(LogTemp, Log, TEXT("%d new memories extracted"), Update.MemoriesExtracted);
for (const FEstuaryMemoryData& Mem : Update.NewMemories)
{
UE_LOG(LogTemp, Log, TEXT(" [%s] %s"), *Mem.MemoryType, *Mem.Content);
}
}

See Data Models for the full memory schema.


Multi-Character Scenes

You can place several AEstuaryCharacter actors with different Character IDs. Each registers independently with the subsystem and receives only its own response stream — there is no cross-talk. The active conversation is with the character ID used in Connect; the others coexist without interfering.


Next Steps