Chat Input
ChatInput draws a box to send a chat message. It returns the message on the run the send starts, and empties the box.
API
Interface
func ChatInput(c *tgframe.Container, placeholder string, conf ...*ChatInputConf) (string, bool)
Parameters
c: Parent container.placeholder: The hint shown in the empty box. It also names the input, so two inputs with the same placeholder collide; give one aConf.ID.conf: Optional configuration, at most one.
// ChatInputConf is the configuration for the ChatInput component.
type ChatInputConf struct {
tgframe.Base // ID
// MaxLength is the most characters a message may have. 0 is no limit.
MaxLength int
// Disabled keeps the app user from sending, e.g. while a reply streams.
Disabled bool
}
Return
The message and true on the run its send starts; "" and false on every
other run. The message is not kept: store it, as a chat keeps its history.
Enter sends, Shift+Enter breaks the line. An Enter that ends an IME composition only picks the characters.
Where it is drawn
Written in the page's main container, p.Main, it is pinned to the bottom of
the page wherever it is written. That lets a page read the message first and
draw the messages it adds after, as the example does. In any other container
it stays where it is written.
Example
type chatTurn struct {
Role string
Text string
}
history := p.State.Default("chat_history", []chatTurn{})
msg, sent := tgcomp.ChatInput(p.Main, "Say something")
for _, turn := range *history {
tgcomp.Markdown(tgcomp.ChatMessage(p.Main, turn.Role), turn.Text)
}
if sent {
// Kept before streaming: a new send cancels this run mid-stream,
// and the message is already gone from the input.
*history = append(*history, chatTurn{Role: "user", Text: msg})
tgcomp.Markdown(tgcomp.ChatMessage(p.Main, "user"), msg)
reply, err := tgcomp.WriteStream(
tgcomp.ChatMessage(p.Main, "assistant"), echo(p.Context, msg))
if err != nil {
return err
}
*history = append(*history, chatTurn{Role: "assistant", Text: reply})
}
echo stands in for a model, streamed with
WriteStream:
// echo stands in for a model: it answers with the message, a word at a time.
func echo(ctx context.Context, msg string) iter.Seq2[string, error] {
return func(yield func(string, error) bool) {
for _, word := range strings.SplitAfter("You said: "+msg, " ") {
select {
case <-ctx.Done():
return
case <-time.After(60 * time.Millisecond):
}
if !yield(word, nil) {
return
}
}
}
}