Write Stream
WriteStream writes a stream of text, such as a model's reply, into one Markdown as it arrives, and returns the whole text once the stream ends.
API
Interface
func WriteStream(c *tgframe.Container, seq iter.Seq2[string, error],
conf ...*WriteStreamConf) (string, error)
Parameters
c: Parent container.seq: The chunks to write. The first error stops the stream; it is returned with the text before it.conf: Optional configuration, at most one.
// WriteStreamConf is the configuration for the WriteStream component.
type WriteStreamConf struct {
tgframe.Base // ID
// Interval is the least time between two sends of the text. Default is
// 50ms.
Interval time.Duration
}
The growing text is sent at most once per Interval, so a stream of one token
per chunk does not resend the whole text once per token.
The stream
seq runs on a goroutine of its own, and must not draw components.
WriteStream waits for it to return before it does, so build it on
Params.Context: a run cut by the next event then ends
the request too, instead of holding up the run after it.
Keeping the text
Like everything else on the page, the streamed text is drawn again only if the
next run writes it again. Keep what WriteStream returns in the
state and draw it with Markdown from
then on, as a chat keeps its history.
Example
if tgcomp.Button(p.Main, "Stream a reply") {
text, err := tgcomp.WriteStream(p.Main, fakeReply(p.Context))
if err != nil {
return err
}
tgcomp.Caption(p.Main, fmt.Sprintf("%d words", len(strings.Fields(text))))
}
// fakeReply stands in for a model's reply: a word at a time.
func fakeReply(ctx context.Context) iter.Seq2[string, error] {
const reply = "**WriteStream** writes the chunks into one Markdown " +
"as they arrive, and returns the whole text once the stream ends."
return func(yield func(string, error) bool) {
for _, word := range strings.SplitAfter(reply, " ") {
select {
case <-ctx.Done():
return
case <-time.After(80 * time.Millisecond):
}
if !yield(word, nil) {
return
}
}
}
}