JavaScript SDK / Talk streams
Talk streams
A TalkMessageStream pushes text to the avatar's voice
as it is produced, token by token from an upstream model for
instance, instead of waiting for a whole utterance. The avatar starts speaking
before the sentence is finished.
import { TalkMessageStream } from "@zeligate/zeli-avatar";You do not construct one. It comes from
session.createTalkMessageStream(),
which requires the box's control gateway.
Sending text
streamMessageChunk(content, endOfSpeech?): Promise<void>
Append a chunk, ending the utterance when endOfSpeech is true.
The next chunk of the utterance.
Set on the final chunk.
endMessage(): Promise<void>
Signal the end of the utterance, if it has not already ended. Safe to call more than once, and a no-op on a stream that was interrupted.
send(text, options?): Promise<void>
The options-object form. It stays alongside streamMessageChunk because it
carries startOfSpeech, which the shorter form has no equivalent for.
The next chunk of the utterance.
Whether this chunk starts the utterance. Left unset, the first chunk is the
start and later ones are not, which is what you want almost always. Set it
explicitly to override: false on the first chunk when you are
continuing an utterance the box has already begun, true on a
later chunk to restart one.
Set on the final chunk.
Sending on a stream that has ended or been interrupted throws a
SessionError, not a bare Error, so a documented
catch (e) { if (e instanceof ZeliError) ... } still covers the single most
likely caller mistake.
Reading state
getState(): TalkStreamState
Where this utterance has got to. Four states, not one boolean:
| State | Meaning |
|---|---|
unstarted | Created, nothing sent yet. |
streaming | Being fed text. |
interrupted | The avatar was cut off partway. |
ended | Finished normally. |
Interruption wins over ending: an utterance the user talked over reports
interrupted even though it is also finished.
isActive(): boolean
Whether this utterance can still take more text. True until it ends or is interrupted.
getCorrelationId(): string
The id tying this stream's chunks to the resulting transcript message. A lowercase UUID v4, dashes included. See Correlation ids.
A boolean cannot answer the question a developer actually asks after a
barge-in: do I retry this, or was it delivered? The old closed
getter read true for both an utterance the user talked over and one that
finished saying what it meant to say, which is exactly the distinction that
matters. It is deprecated for that reason, not for its spelling.
Example
import { ZeliEvent } from "@zeligate/zeli-avatar";
const talk = session.createTalkMessageStream({ tone: "confident" });
for await (const token of model.stream(prompt)) {
if (!talk.isActive()) break; // the listener barged in
await talk.streamMessageChunk(token);
}
await talk.endMessage();
if (talk.getState() === "interrupted") {
console.log("cut off:", talk.getCorrelationId());
}Barge-in from a UI control:
stopButton.onclick = () => session.interruptPersona(talk.getCorrelationId());
client.on(ZeliEvent.TALK_STREAM_INTERRUPTED, (correlationId) => {
console.log("box stopped", correlationId);
});await using works too, since Symbol.asyncDispose calls endMessage():
await using talk = session.createTalkMessageStream();
await talk.streamMessageChunk("Hello ");
await talk.streamMessageChunk("world.");
// endMessage() runs at scope exitDeprecated aliases
| Deprecated | Use instead |
|---|---|
TalkStream (the exported name) | TalkMessageStream |
closed (getter) | getState() or isActive() |
close() | endMessage() |
TalkStream is an alias of the class rather than a subclass, so instanceof
holds both ways and a value created under one name is accepted anywhere the
other is expected. The aliases are removed at 1.0.