@llblab/pi-telegram 0.6.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +153 -0
- package/BACKLOG.md +5 -0
- package/CHANGELOG.md +148 -0
- package/README.md +40 -39
- package/docs/README.md +1 -0
- package/docs/architecture.md +36 -27
- package/docs/attachment-handlers.md +4 -6
- package/docs/callback-namespaces.md +36 -0
- package/docs/command-templates.md +53 -9
- package/docs/locks.md +4 -0
- package/docs/outbound-handlers.md +6 -5
- package/index.ts +60 -7
- package/lib/api.ts +1 -0
- package/lib/attachment-handlers.ts +21 -10
- package/lib/attachments.ts +1 -0
- package/lib/command-templates.ts +37 -3
- package/lib/commands.ts +363 -88
- package/lib/config.ts +6 -2
- package/lib/keyboard.ts +14 -0
- package/lib/lifecycle.ts +26 -0
- package/lib/locks.ts +3 -2
- package/lib/media.ts +1 -0
- package/lib/menu-model.ts +881 -0
- package/lib/menu-queue.ts +610 -0
- package/lib/menu-status.ts +226 -0
- package/lib/menu-thinking.ts +171 -0
- package/lib/menu.ts +143 -1019
- package/lib/model.ts +1 -0
- package/lib/outbound-handlers.ts +28 -19
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +1 -0
- package/lib/preview.ts +97 -50
- package/lib/prompt-templates.ts +150 -0
- package/lib/prompts.ts +2 -0
- package/lib/queue.ts +60 -15
- package/lib/rendering.ts +1 -0
- package/lib/replies.ts +90 -2
- package/lib/routing.ts +106 -14
- package/lib/runtime.ts +2 -0
- package/lib/setup.ts +1 -0
- package/lib/status.ts +18 -6
- package/lib/turns.ts +1 -0
- package/lib/updates.ts +55 -6
- package/package.json +5 -2
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram queue menu UI helpers
|
|
3
|
+
* Zones: telegram ui, queue controls, menu composition
|
|
4
|
+
* Owns queue-menu rendering, queue item callbacks, and queue-menu runtime adapters while core queue mechanics stay in queue
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
8
|
+
import type { TelegramModelMenuState } from "./menu.ts";
|
|
9
|
+
import type { MenuModel } from "./model.ts";
|
|
10
|
+
import * as Queue from "./queue.ts";
|
|
11
|
+
|
|
12
|
+
// --- Queue Menu ---
|
|
13
|
+
|
|
14
|
+
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
15
|
+
interface TelegramQueueMenuItem {
|
|
16
|
+
chatId: number;
|
|
17
|
+
replyToMessageId: number;
|
|
18
|
+
isPriority: boolean;
|
|
19
|
+
hasAttachments: boolean;
|
|
20
|
+
statusSummary: string;
|
|
21
|
+
promptText: string;
|
|
22
|
+
}
|
|
23
|
+
function getTelegramQueueItemPromptText<Context>(
|
|
24
|
+
item: Queue.TelegramQueueItem<Context>,
|
|
25
|
+
): string {
|
|
26
|
+
if (item.kind !== "prompt") return item.statusSummary;
|
|
27
|
+
return (
|
|
28
|
+
item.content
|
|
29
|
+
.filter((block) => block.type === "text")
|
|
30
|
+
.map((block) => block.text)
|
|
31
|
+
.join("\n")
|
|
32
|
+
.trim() || item.statusSummary
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function toTelegramQueueMenuItems<Context>(
|
|
36
|
+
items: readonly Queue.TelegramQueueItem<Context>[],
|
|
37
|
+
): TelegramQueueMenuItem[] {
|
|
38
|
+
return items.map(function toTelegramQueueMenuItem(item) {
|
|
39
|
+
return {
|
|
40
|
+
chatId: item.chatId,
|
|
41
|
+
replyToMessageId: item.replyToMessageId,
|
|
42
|
+
isPriority: item.queueLane === "priority",
|
|
43
|
+
hasAttachments:
|
|
44
|
+
item.kind === "prompt" && item.queuedAttachments.length > 0,
|
|
45
|
+
statusSummary: item.statusSummary,
|
|
46
|
+
promptText: getTelegramQueueItemPromptText(item),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function buildTelegramQueueMenuReplyMarkup(
|
|
51
|
+
items: readonly TelegramQueueMenuItem[],
|
|
52
|
+
): TelegramQueueMenuReplyMarkup {
|
|
53
|
+
const backRow = [{ text: "⬆️ Main menu", callback_data: "menu:back" }];
|
|
54
|
+
if (items.length === 0) return { inline_keyboard: [backRow] };
|
|
55
|
+
const rows = items.map(function buildTelegramQueueMenuRow(item, index) {
|
|
56
|
+
const prefix = item.isPriority ? "⚡ " : item.hasAttachments ? "📎 " : "";
|
|
57
|
+
const label = `${index + 1}. ${prefix}${item.statusSummary}`;
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
text: label,
|
|
61
|
+
callback_data: `queue:pick:${item.chatId}:${item.replyToMessageId}`,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
});
|
|
65
|
+
return { inline_keyboard: [backRow, ...rows] };
|
|
66
|
+
}
|
|
67
|
+
function findTelegramQueueItem<Context>(
|
|
68
|
+
items: readonly Queue.TelegramQueueItem<Context>[],
|
|
69
|
+
chatId: number,
|
|
70
|
+
replyToMessageId: number,
|
|
71
|
+
): Queue.TelegramQueueItem<Context> | undefined {
|
|
72
|
+
return items.find(function matchesTelegramQueueItem(item) {
|
|
73
|
+
return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function findTelegramQueueMenuItem(
|
|
77
|
+
items: readonly TelegramQueueMenuItem[],
|
|
78
|
+
chatId: number,
|
|
79
|
+
replyToMessageId: number,
|
|
80
|
+
): TelegramQueueMenuItem | undefined {
|
|
81
|
+
return items.find(function matchesTelegramQueueMenuItem(item) {
|
|
82
|
+
return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function escapeTelegramQueueMenuHtml(text: string): string {
|
|
86
|
+
return text
|
|
87
|
+
.replace(/&/g, "&")
|
|
88
|
+
.replace(/</g, "<")
|
|
89
|
+
.replace(/>/g, ">");
|
|
90
|
+
}
|
|
91
|
+
function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
92
|
+
return escapeTelegramQueueMenuHtml(item.promptText);
|
|
93
|
+
}
|
|
94
|
+
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
95
|
+
chatId: number,
|
|
96
|
+
replyToMessageId: number,
|
|
97
|
+
isPriority: boolean,
|
|
98
|
+
): TelegramQueueMenuReplyMarkup {
|
|
99
|
+
const priorityLabel = isPriority ? "🐢 Deprioritize" : "⚡ Prioritize";
|
|
100
|
+
return {
|
|
101
|
+
inline_keyboard: [
|
|
102
|
+
[{ text: "⬆️ Back", callback_data: "queue:list" }],
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
text: priorityLabel,
|
|
106
|
+
callback_data: `queue:prio:${chatId}:${replyToMessageId}`,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
{
|
|
111
|
+
text: "❌ Cancel",
|
|
112
|
+
callback_data: `queue:cancel:${chatId}:${replyToMessageId}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
119
|
+
getQueuedItems: () => TelegramQueueMenuItem[];
|
|
120
|
+
findItem: (
|
|
121
|
+
chatId: number,
|
|
122
|
+
replyToMessageId: number,
|
|
123
|
+
) => TelegramQueueMenuItem | undefined;
|
|
124
|
+
togglePriority: (chatId: number, replyToMessageId: number) => boolean;
|
|
125
|
+
cancelItem: (
|
|
126
|
+
chatId: number,
|
|
127
|
+
replyToMessageId: number,
|
|
128
|
+
ctx: Context,
|
|
129
|
+
) => boolean;
|
|
130
|
+
updateQueueMessage: (
|
|
131
|
+
chatId: number,
|
|
132
|
+
messageId: number,
|
|
133
|
+
text: string,
|
|
134
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
135
|
+
) => Promise<number | undefined>;
|
|
136
|
+
answerCallbackQuery: (
|
|
137
|
+
callbackQueryId: string,
|
|
138
|
+
text?: string,
|
|
139
|
+
) => Promise<void>;
|
|
140
|
+
updateStatus: (ctx: Context) => void;
|
|
141
|
+
}
|
|
142
|
+
async function handleTelegramQueueMenuCallback<Context>(
|
|
143
|
+
callbackQueryId: string,
|
|
144
|
+
data: string,
|
|
145
|
+
replyChatId: number,
|
|
146
|
+
replyMessageId: number,
|
|
147
|
+
ctx: Context,
|
|
148
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
149
|
+
): Promise<boolean> {
|
|
150
|
+
if (!data.startsWith("queue:")) return false;
|
|
151
|
+
if (data === "queue:noop") {
|
|
152
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (data === "queue:list") {
|
|
156
|
+
await updateTelegramQueueMenuList(
|
|
157
|
+
callbackQueryId,
|
|
158
|
+
replyChatId,
|
|
159
|
+
replyMessageId,
|
|
160
|
+
deps,
|
|
161
|
+
);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
const pickMatch = data.match(/^queue:pick:(\d+):(\d+)$/);
|
|
165
|
+
if (pickMatch) {
|
|
166
|
+
await handleTelegramQueueMenuPick(
|
|
167
|
+
callbackQueryId,
|
|
168
|
+
replyChatId,
|
|
169
|
+
replyMessageId,
|
|
170
|
+
Number(pickMatch[1]),
|
|
171
|
+
Number(pickMatch[2]),
|
|
172
|
+
deps,
|
|
173
|
+
);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const prioMatch = data.match(/^queue:prio:(\d+):(\d+)$/);
|
|
177
|
+
if (prioMatch) {
|
|
178
|
+
await handleTelegramQueueMenuPriority(
|
|
179
|
+
callbackQueryId,
|
|
180
|
+
replyChatId,
|
|
181
|
+
replyMessageId,
|
|
182
|
+
Number(prioMatch[1]),
|
|
183
|
+
Number(prioMatch[2]),
|
|
184
|
+
ctx,
|
|
185
|
+
deps,
|
|
186
|
+
);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const cancelMatch = data.match(/^queue:cancel:(\d+):(\d+)$/);
|
|
190
|
+
if (cancelMatch) {
|
|
191
|
+
await handleTelegramQueueMenuCancel(
|
|
192
|
+
callbackQueryId,
|
|
193
|
+
replyChatId,
|
|
194
|
+
replyMessageId,
|
|
195
|
+
Number(cancelMatch[1]),
|
|
196
|
+
Number(cancelMatch[2]),
|
|
197
|
+
ctx,
|
|
198
|
+
deps,
|
|
199
|
+
);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
function getTelegramQueueMenuListText(
|
|
205
|
+
items: readonly TelegramQueueMenuItem[],
|
|
206
|
+
): string {
|
|
207
|
+
if (items.length === 0) return "<b>Queue is empty.</b>";
|
|
208
|
+
return "<b>Queue:</b>";
|
|
209
|
+
}
|
|
210
|
+
async function updateTelegramQueueMenuList<Context>(
|
|
211
|
+
callbackQueryId: string,
|
|
212
|
+
replyChatId: number,
|
|
213
|
+
replyMessageId: number,
|
|
214
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
215
|
+
notice?: string,
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const items = deps.getQueuedItems();
|
|
218
|
+
await deps.updateQueueMessage(
|
|
219
|
+
replyChatId,
|
|
220
|
+
replyMessageId,
|
|
221
|
+
getTelegramQueueMenuListText(items),
|
|
222
|
+
buildTelegramQueueMenuReplyMarkup(items),
|
|
223
|
+
);
|
|
224
|
+
await deps.answerCallbackQuery(callbackQueryId, notice);
|
|
225
|
+
}
|
|
226
|
+
async function refreshStaleTelegramQueueMenuItem<Context>(
|
|
227
|
+
callbackQueryId: string,
|
|
228
|
+
replyChatId: number,
|
|
229
|
+
replyMessageId: number,
|
|
230
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
await updateTelegramQueueMenuList(
|
|
233
|
+
callbackQueryId,
|
|
234
|
+
replyChatId,
|
|
235
|
+
replyMessageId,
|
|
236
|
+
deps,
|
|
237
|
+
"Item no longer in queue.",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
async function handleTelegramQueueMenuPick<Context>(
|
|
241
|
+
callbackQueryId: string,
|
|
242
|
+
replyChatId: number,
|
|
243
|
+
replyMessageId: number,
|
|
244
|
+
chatId: number,
|
|
245
|
+
msgId: number,
|
|
246
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const item = deps.findItem(chatId, msgId);
|
|
249
|
+
if (!item) {
|
|
250
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
251
|
+
callbackQueryId,
|
|
252
|
+
replyChatId,
|
|
253
|
+
replyMessageId,
|
|
254
|
+
deps,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
await deps.updateQueueMessage(
|
|
258
|
+
replyChatId,
|
|
259
|
+
replyMessageId,
|
|
260
|
+
getTelegramQueueMenuItemText(item),
|
|
261
|
+
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, item.isPriority),
|
|
262
|
+
);
|
|
263
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
264
|
+
}
|
|
265
|
+
async function handleTelegramQueueMenuPriority<Context>(
|
|
266
|
+
callbackQueryId: string,
|
|
267
|
+
replyChatId: number,
|
|
268
|
+
replyMessageId: number,
|
|
269
|
+
chatId: number,
|
|
270
|
+
msgId: number,
|
|
271
|
+
ctx: Context,
|
|
272
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
273
|
+
): Promise<void> {
|
|
274
|
+
const item = deps.findItem(chatId, msgId);
|
|
275
|
+
if (!item) {
|
|
276
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
277
|
+
callbackQueryId,
|
|
278
|
+
replyChatId,
|
|
279
|
+
replyMessageId,
|
|
280
|
+
deps,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
deps.togglePriority(chatId, msgId);
|
|
284
|
+
deps.updateStatus(ctx);
|
|
285
|
+
const updated = deps.findItem(chatId, msgId);
|
|
286
|
+
const newPriority = updated?.isPriority ?? !item.isPriority;
|
|
287
|
+
await deps.updateQueueMessage(
|
|
288
|
+
replyChatId,
|
|
289
|
+
replyMessageId,
|
|
290
|
+
getTelegramQueueMenuItemText(item),
|
|
291
|
+
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, newPriority),
|
|
292
|
+
);
|
|
293
|
+
await deps.answerCallbackQuery(
|
|
294
|
+
callbackQueryId,
|
|
295
|
+
newPriority ? "Prioritized." : "Deprioritized.",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
async function handleTelegramQueueMenuCancel<Context>(
|
|
299
|
+
callbackQueryId: string,
|
|
300
|
+
replyChatId: number,
|
|
301
|
+
replyMessageId: number,
|
|
302
|
+
chatId: number,
|
|
303
|
+
msgId: number,
|
|
304
|
+
ctx: Context,
|
|
305
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
306
|
+
): Promise<void> {
|
|
307
|
+
const removed = deps.cancelItem(chatId, msgId, ctx);
|
|
308
|
+
deps.updateStatus(ctx);
|
|
309
|
+
await updateTelegramQueueMenuList(
|
|
310
|
+
callbackQueryId,
|
|
311
|
+
replyChatId,
|
|
312
|
+
replyMessageId,
|
|
313
|
+
deps,
|
|
314
|
+
removed ? "Removed from queue." : "Item not found.",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
interface TelegramQueueMenuCallbackQuery {
|
|
319
|
+
id: string;
|
|
320
|
+
data?: string;
|
|
321
|
+
message?: { chat?: { id?: number }; message_id?: number };
|
|
322
|
+
}
|
|
323
|
+
interface TelegramQueueMenuRuntime<Context> {
|
|
324
|
+
openQueueMenu: (
|
|
325
|
+
chatId: number,
|
|
326
|
+
replyToMessageId: number,
|
|
327
|
+
ctx: Context,
|
|
328
|
+
) => Promise<void>;
|
|
329
|
+
handleCallbackQuery: (
|
|
330
|
+
query: TelegramQueueMenuCallbackQuery,
|
|
331
|
+
ctx: Context,
|
|
332
|
+
) => Promise<boolean>;
|
|
333
|
+
}
|
|
334
|
+
export function createTelegramQueueMenuRuntime<
|
|
335
|
+
Context,
|
|
336
|
+
TModel extends MenuModel = MenuModel,
|
|
337
|
+
>(deps: {
|
|
338
|
+
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
|
339
|
+
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
340
|
+
sendInteractiveMessage: (
|
|
341
|
+
chatId: number,
|
|
342
|
+
text: string,
|
|
343
|
+
mode: "html",
|
|
344
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
345
|
+
) => Promise<number | undefined>;
|
|
346
|
+
editInteractiveMessage: (
|
|
347
|
+
chatId: number,
|
|
348
|
+
messageId: number,
|
|
349
|
+
text: string,
|
|
350
|
+
mode: "html",
|
|
351
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
352
|
+
) => Promise<void>;
|
|
353
|
+
answerCallbackQuery: (
|
|
354
|
+
callbackQueryId: string,
|
|
355
|
+
text?: string,
|
|
356
|
+
) => Promise<void>;
|
|
357
|
+
getModelMenuState: (
|
|
358
|
+
chatId: number,
|
|
359
|
+
ctx: Context,
|
|
360
|
+
) => Promise<TelegramModelMenuState<TModel>>;
|
|
361
|
+
getStoredModelMenuState: (
|
|
362
|
+
messageId: number | undefined,
|
|
363
|
+
) => TelegramModelMenuState<TModel> | undefined;
|
|
364
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
365
|
+
updateStatusMessage: (
|
|
366
|
+
state: TelegramModelMenuState<TModel>,
|
|
367
|
+
ctx: Context,
|
|
368
|
+
) => Promise<void>;
|
|
369
|
+
updateStatus: (ctx: Context) => void;
|
|
370
|
+
}): TelegramQueueMenuRuntime<Context> {
|
|
371
|
+
const sendQueueMenuMessage = createQueueMenuSendMessageAdapter(
|
|
372
|
+
deps.sendInteractiveMessage,
|
|
373
|
+
);
|
|
374
|
+
const editQueueMenuMessage = createQueueMenuEditMessageAdapter(
|
|
375
|
+
deps.editInteractiveMessage,
|
|
376
|
+
);
|
|
377
|
+
return {
|
|
378
|
+
openQueueMenu: createOpenQueueMenu<Context, TModel>({
|
|
379
|
+
getQueuedItems: deps.telegramQueueStore.getQueuedItems,
|
|
380
|
+
getModelMenuState: deps.getModelMenuState,
|
|
381
|
+
storeModelMenuState: deps.storeModelMenuState,
|
|
382
|
+
sendInteractiveMessage: sendQueueMenuMessage,
|
|
383
|
+
}),
|
|
384
|
+
handleCallbackQuery: createQueueMenuCallbackHandler<Context, TModel>({
|
|
385
|
+
telegramQueueStore: deps.telegramQueueStore,
|
|
386
|
+
queueMutationRuntime: deps.queueMutationRuntime,
|
|
387
|
+
editInteractiveMessage: editQueueMenuMessage,
|
|
388
|
+
getStoredModelMenuState: deps.getStoredModelMenuState,
|
|
389
|
+
updateStatusMessage: deps.updateStatusMessage,
|
|
390
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
391
|
+
updateStatus: deps.updateStatus,
|
|
392
|
+
}),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function createOpenQueueMenu<
|
|
396
|
+
Context,
|
|
397
|
+
TModel extends MenuModel = MenuModel,
|
|
398
|
+
>(deps: {
|
|
399
|
+
getQueuedItems: () => Queue.TelegramQueueItem<Context>[];
|
|
400
|
+
getModelMenuState: (
|
|
401
|
+
chatId: number,
|
|
402
|
+
ctx: Context,
|
|
403
|
+
) => Promise<TelegramModelMenuState<TModel>>;
|
|
404
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
405
|
+
sendInteractiveMessage: (
|
|
406
|
+
chatId: number,
|
|
407
|
+
replyToMessageId: number,
|
|
408
|
+
text: string,
|
|
409
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
410
|
+
) => Promise<number | undefined>;
|
|
411
|
+
}) {
|
|
412
|
+
return async function openQueueMenu(
|
|
413
|
+
chatId: number,
|
|
414
|
+
replyToMessageId: number,
|
|
415
|
+
ctx: Context,
|
|
416
|
+
): Promise<void> {
|
|
417
|
+
const state = await deps.getModelMenuState(chatId, ctx);
|
|
418
|
+
const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems());
|
|
419
|
+
const text = getTelegramQueueMenuListText(menuItems);
|
|
420
|
+
const messageId = await deps.sendInteractiveMessage(
|
|
421
|
+
chatId,
|
|
422
|
+
replyToMessageId,
|
|
423
|
+
text,
|
|
424
|
+
buildTelegramQueueMenuReplyMarkup(menuItems),
|
|
425
|
+
);
|
|
426
|
+
if (messageId === undefined) return;
|
|
427
|
+
state.messageId = messageId;
|
|
428
|
+
state.mode = "queue";
|
|
429
|
+
deps.storeModelMenuState(state);
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function createQueueMenuCallbackHandler<
|
|
433
|
+
Context,
|
|
434
|
+
TModel extends MenuModel = MenuModel,
|
|
435
|
+
>(deps: {
|
|
436
|
+
telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
|
|
437
|
+
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
438
|
+
editInteractiveMessage: (
|
|
439
|
+
chatId: number,
|
|
440
|
+
messageId: number,
|
|
441
|
+
text: string,
|
|
442
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
443
|
+
) => Promise<number | undefined>;
|
|
444
|
+
getStoredModelMenuState: (
|
|
445
|
+
messageId: number | undefined,
|
|
446
|
+
) => TelegramModelMenuState<TModel> | undefined;
|
|
447
|
+
updateStatusMessage: (
|
|
448
|
+
state: TelegramModelMenuState<TModel>,
|
|
449
|
+
ctx: Context,
|
|
450
|
+
) => Promise<void>;
|
|
451
|
+
answerCallbackQuery: (
|
|
452
|
+
callbackQueryId: string,
|
|
453
|
+
text?: string,
|
|
454
|
+
) => Promise<void>;
|
|
455
|
+
updateStatus: (ctx: Context) => void;
|
|
456
|
+
}) {
|
|
457
|
+
return async function queueMenuCallbackHandler(
|
|
458
|
+
query: TelegramQueueMenuCallbackQuery,
|
|
459
|
+
ctx: Context,
|
|
460
|
+
): Promise<boolean> {
|
|
461
|
+
const data = query.data;
|
|
462
|
+
const chatId = query.message?.chat?.id;
|
|
463
|
+
const messageId = query.message?.message_id;
|
|
464
|
+
if (!data || typeof chatId !== "number" || typeof messageId !== "number")
|
|
465
|
+
return false;
|
|
466
|
+
if (data === "status:queue") {
|
|
467
|
+
const state = deps.getStoredModelMenuState(messageId);
|
|
468
|
+
if (!state) {
|
|
469
|
+
await deps.answerCallbackQuery(
|
|
470
|
+
query.id,
|
|
471
|
+
"Interactive message expired.",
|
|
472
|
+
);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
const menuItems = toTelegramQueueMenuItems(
|
|
476
|
+
deps.telegramQueueStore.getQueuedItems(),
|
|
477
|
+
);
|
|
478
|
+
await deps.editInteractiveMessage(
|
|
479
|
+
chatId,
|
|
480
|
+
messageId,
|
|
481
|
+
getTelegramQueueMenuListText(menuItems),
|
|
482
|
+
buildTelegramQueueMenuReplyMarkup(menuItems),
|
|
483
|
+
);
|
|
484
|
+
state.mode = "queue";
|
|
485
|
+
await deps.answerCallbackQuery(query.id);
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
if (!data.startsWith("queue:")) return false;
|
|
489
|
+
const getQueueSnapshot = function getQueueSnapshot() {
|
|
490
|
+
return deps.telegramQueueStore.getQueuedItems();
|
|
491
|
+
};
|
|
492
|
+
const toMenuItems = function toMenuItems() {
|
|
493
|
+
return toTelegramQueueMenuItems(getQueueSnapshot());
|
|
494
|
+
};
|
|
495
|
+
const findItem = function findItem(cId: number, rId: number) {
|
|
496
|
+
return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
|
|
497
|
+
};
|
|
498
|
+
return handleTelegramQueueMenuCallback(
|
|
499
|
+
query.id,
|
|
500
|
+
data,
|
|
501
|
+
chatId,
|
|
502
|
+
messageId,
|
|
503
|
+
ctx,
|
|
504
|
+
{
|
|
505
|
+
getQueuedItems: toMenuItems,
|
|
506
|
+
findItem,
|
|
507
|
+
togglePriority: function togglePriority(cId, rId) {
|
|
508
|
+
return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
|
|
509
|
+
getQueueSnapshot,
|
|
510
|
+
queueMutationRuntime: deps.queueMutationRuntime,
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
cancelItem: function cancelItem(cId, rId, c) {
|
|
514
|
+
return cancelQueuedTelegramItem(cId, rId, c, {
|
|
515
|
+
getQueueSnapshot,
|
|
516
|
+
queueMutationRuntime: deps.queueMutationRuntime,
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
updateQueueMessage: deps.editInteractiveMessage,
|
|
520
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
521
|
+
updateStatus: deps.updateStatus,
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function toggleQueuedTelegramPromptPriority<Context>(
|
|
527
|
+
chatId: number,
|
|
528
|
+
replyToMessageId: number,
|
|
529
|
+
ctx: Context,
|
|
530
|
+
deps: {
|
|
531
|
+
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
|
532
|
+
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
533
|
+
},
|
|
534
|
+
): boolean {
|
|
535
|
+
const item = findTelegramQueueItem(
|
|
536
|
+
deps.getQueueSnapshot(),
|
|
537
|
+
chatId,
|
|
538
|
+
replyToMessageId,
|
|
539
|
+
);
|
|
540
|
+
if (!item) return false;
|
|
541
|
+
if (item.queueLane === "priority") {
|
|
542
|
+
deps.queueMutationRuntime.clearPriorityByMessageId(replyToMessageId, ctx);
|
|
543
|
+
} else {
|
|
544
|
+
deps.queueMutationRuntime.prioritizeByMessageId(replyToMessageId, ctx);
|
|
545
|
+
}
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
function cancelQueuedTelegramItem<Context>(
|
|
549
|
+
chatId: number,
|
|
550
|
+
replyToMessageId: number,
|
|
551
|
+
ctx: Context,
|
|
552
|
+
deps: {
|
|
553
|
+
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
|
554
|
+
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
555
|
+
},
|
|
556
|
+
): boolean {
|
|
557
|
+
const item = findTelegramQueueItem(
|
|
558
|
+
deps.getQueueSnapshot(),
|
|
559
|
+
chatId,
|
|
560
|
+
replyToMessageId,
|
|
561
|
+
);
|
|
562
|
+
if (!item) return false;
|
|
563
|
+
return (
|
|
564
|
+
deps.queueMutationRuntime.removeByMessageIds([item.replyToMessageId], ctx) >
|
|
565
|
+
0
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
function createQueueMenuSendMessageAdapter(
|
|
569
|
+
sendInteractiveMessage: (
|
|
570
|
+
chatId: number,
|
|
571
|
+
text: string,
|
|
572
|
+
mode: "html",
|
|
573
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
574
|
+
) => Promise<number | undefined>,
|
|
575
|
+
) {
|
|
576
|
+
return function queueMenuSendMessage(
|
|
577
|
+
chatId: number,
|
|
578
|
+
_replyToMessageId: number,
|
|
579
|
+
text: string,
|
|
580
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
581
|
+
): Promise<number | undefined> {
|
|
582
|
+
return sendInteractiveMessage(chatId, text, "html", replyMarkup);
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function createQueueMenuEditMessageAdapter(
|
|
586
|
+
editInteractiveMessage: (
|
|
587
|
+
chatId: number,
|
|
588
|
+
messageId: number,
|
|
589
|
+
text: string,
|
|
590
|
+
mode: "html",
|
|
591
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
592
|
+
) => Promise<void>,
|
|
593
|
+
) {
|
|
594
|
+
return function queueMenuEditMessage(
|
|
595
|
+
chatId: number,
|
|
596
|
+
messageId: number,
|
|
597
|
+
text: string,
|
|
598
|
+
replyMarkup: TelegramQueueMenuReplyMarkup,
|
|
599
|
+
): Promise<number | undefined> {
|
|
600
|
+
return editInteractiveMessage(
|
|
601
|
+
chatId,
|
|
602
|
+
messageId,
|
|
603
|
+
text,
|
|
604
|
+
"html",
|
|
605
|
+
replyMarkup,
|
|
606
|
+
).then(function () {
|
|
607
|
+
return undefined as number | undefined;
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
}
|