Skip to content

Commit 121d093

Browse files
committed
docs(examples): add richdraft streaming example
examples/richdraft streams a rich message draft block by block via SendRichMessageDraft (reusing one draft id for the ephemeral ~30s preview), then persists the finished message with SendRichMessage — the live-preview pattern an AI bot uses while generating. Content is built with the rich package (rich.New(...).Input()).
1 parent 6ef01d8 commit 121d093

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

examples/richdraft/main.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Command richdraft demonstrates streaming a rich message draft (Bot API 10.1)
2+
// with github.com/gotd/botapi: the pattern an AI bot uses to show a live preview
3+
// while it generates output.
4+
//
5+
// On /stream the bot builds a rich message incrementally and, after each new
6+
// block, calls SendRichMessageDraft with the SAME draft id. Each call replaces an
7+
// ephemeral ~30-second preview in the chat (it does not persist). When the
8+
// "generation" finishes, the bot calls SendRichMessage once to persist the
9+
// finished message.
10+
//
11+
// The draft content is a tg.InputRichMessageClass, built exactly as for
12+
// SendRichMessage: from native page blocks with rich.New(...).Input(), or — for a
13+
// whole document parsed by Telegram's servers — rich.HTML / rich.Markdown.
14+
//
15+
// Run it with an MTProto app identity (https://my.telegram.org) and a BotFather
16+
// token:
17+
//
18+
// APP_ID=12345 APP_HASH=abcdef BOT_TOKEN=123:abc go run ./examples/richdraft
19+
package main
20+
21+
import (
22+
"context"
23+
"os"
24+
"os/signal"
25+
"strconv"
26+
"time"
27+
28+
"github.com/gotd/log/logzap"
29+
"go.uber.org/zap"
30+
31+
"github.com/gotd/td/telegram/message/rich"
32+
"github.com/gotd/td/tg"
33+
34+
"github.com/gotd/botapi"
35+
"github.com/gotd/botapi/storage"
36+
)
37+
38+
func main() {
39+
log, _ := zap.NewProduction()
40+
defer func() { _ = log.Sync() }()
41+
42+
appID, err := strconv.Atoi(os.Getenv("APP_ID"))
43+
if err != nil {
44+
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
45+
}
46+
47+
// Persist session, peers and update state so the bot resumes across restarts.
48+
store, err := storage.Open("session.bbolt")
49+
if err != nil {
50+
log.Fatal("Open storage", zap.Error(err))
51+
}
52+
53+
defer func() { _ = store.Close() }()
54+
55+
bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
56+
AppID: appID,
57+
AppHash: os.Getenv("APP_HASH"),
58+
Logger: logzap.New(log),
59+
Storage: store,
60+
})
61+
if err != nil {
62+
log.Fatal("Create bot", zap.Error(err))
63+
}
64+
65+
bot.Use(botapi.Recover(), botapi.Logging())
66+
67+
bot.OnCommand("start", "Explain the demo", func(c *botapi.Context) error {
68+
_, err := c.Reply("Send /stream to watch a rich message stream in, then persist.")
69+
return err
70+
})
71+
72+
bot.OnCommand("stream", "Stream a rich message draft, then finalize it", stream)
73+
74+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
75+
defer cancel()
76+
77+
log.Info("Starting richdraft bot")
78+
79+
if err := bot.Run(ctx); err != nil {
80+
log.Fatal("Run", zap.Error(err))
81+
}
82+
}
83+
84+
// stream simulates a bot generating a rich message block by block, previewing
85+
// each step as a draft and persisting the result at the end.
86+
func stream(c *botapi.Context) error {
87+
chat, ok := c.Chat()
88+
if !ok {
89+
return nil
90+
}
91+
92+
// A non-zero id that identifies this draft; reuse it across the whole stream
93+
// so each call updates the same preview instead of starting a new one.
94+
draftID := time.Now().UnixNano()
95+
96+
// The body grows one block at a time, as if generated token by token.
97+
body := []tg.PageBlockClass{rich.Heading2(rich.Plain("Streaming a rich message"))}
98+
99+
for _, part := range []string{
100+
"First, the bot drafts an opening paragraph…",
101+
"…then it adds detail: every SendRichMessageDraft call updates the same ephemeral preview.",
102+
"Finally it wraps up. Nothing is persisted until the bot sends the finished message.",
103+
} {
104+
body = append(body, rich.Paragraph(rich.Plain(part)))
105+
106+
// Preview the growing partial message. rich.New(...).Input() yields the
107+
// same tg.InputRichMessageClass SendRichMessage takes; rich.Markdown /
108+
// rich.HTML would work here too for server-parsed documents.
109+
if err := c.Bot.SendRichMessageDraft(c, chat, draftID, rich.New(body...).Input()); err != nil {
110+
return err
111+
}
112+
113+
// Stand in for generation latency. The preview is ephemeral (~30s).
114+
time.Sleep(800 * time.Millisecond)
115+
}
116+
117+
// Generation finished: persist the complete message with SendRichMessage.
118+
body = append(body, rich.Footer(rich.Plain("sent with SendRichMessage")))
119+
120+
_, err := c.Bot.SendRichMessage(c, chat, rich.New(body...).Input())
121+
122+
return err
123+
}

0 commit comments

Comments
 (0)