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
			}
		}
	}
}