@llblab/pi-telegram 0.8.1 → 0.9.0
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 +1 -0
- package/CHANGELOG.md +17 -1
- package/README.md +7 -4
- package/docs/architecture.md +9 -6
- package/index.ts +45 -2
- package/lib/commands.ts +72 -0
- package/lib/config.ts +49 -0
- package/lib/inbound-handlers.ts +15 -2
- package/lib/locks.ts +16 -0
- package/lib/menu-model.ts +291 -19
- package/lib/menu-queue.ts +137 -36
- package/lib/menu-settings.ts +272 -0
- package/lib/menu-status.ts +15 -2
- package/lib/menu-thinking.ts +2 -2
- package/lib/menu.ts +45 -3
- package/lib/pi.ts +16 -0
- package/lib/preview.ts +15 -0
- package/lib/prompts.ts +24 -1
- package/lib/queue.ts +53 -12
- package/lib/routing.ts +26 -0
- package/lib/status.ts +20 -6
- package/package.json +1 -1
package/lib/menu-queue.ts
CHANGED
|
@@ -17,6 +17,7 @@ type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
17
17
|
interface TelegramQueueMenuItem {
|
|
18
18
|
chatId: number;
|
|
19
19
|
replyToMessageId: number;
|
|
20
|
+
queuePosition: number;
|
|
20
21
|
isPriority: boolean;
|
|
21
22
|
priorityEmoji?: string;
|
|
22
23
|
hasAttachments: boolean;
|
|
@@ -38,10 +39,11 @@ function getTelegramQueueItemPromptText<Context>(
|
|
|
38
39
|
function toTelegramQueueMenuItems<Context>(
|
|
39
40
|
items: readonly Queue.TelegramQueueItem<Context>[],
|
|
40
41
|
): TelegramQueueMenuItem[] {
|
|
41
|
-
return items.map(function toTelegramQueueMenuItem(item) {
|
|
42
|
+
return items.map(function toTelegramQueueMenuItem(item, index) {
|
|
42
43
|
return {
|
|
43
44
|
chatId: item.chatId,
|
|
44
45
|
replyToMessageId: item.replyToMessageId,
|
|
46
|
+
queuePosition: index + 1,
|
|
45
47
|
isPriority: item.queueLane === "priority",
|
|
46
48
|
priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
|
|
47
49
|
hasAttachments:
|
|
@@ -55,7 +57,10 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
55
57
|
items: readonly TelegramQueueMenuItem[],
|
|
56
58
|
): TelegramQueueMenuReplyMarkup {
|
|
57
59
|
const backRow = [{ text: "⬆️ Main menu", callback_data: "menu:back" }];
|
|
58
|
-
if (items.length === 0)
|
|
60
|
+
if (items.length === 0) {
|
|
61
|
+
const refreshRow = [{ text: "🌀 Refresh", callback_data: "queue:refresh" }];
|
|
62
|
+
return { inline_keyboard: [backRow, refreshRow] };
|
|
63
|
+
}
|
|
59
64
|
const rows = items.map(function buildTelegramQueueMenuRow(item, index) {
|
|
60
65
|
const prefix = item.isPriority
|
|
61
66
|
? `${item.priorityEmoji ?? "⚡"} `
|
|
@@ -100,7 +105,9 @@ function escapeTelegramQueueMenuHtml(text: string): string {
|
|
|
100
105
|
return Array.from(text).map(escapeTelegramQueueMenuHtmlChar).join("");
|
|
101
106
|
}
|
|
102
107
|
function escapeTelegramQueueMenuHtmlPreview(text: string): string {
|
|
103
|
-
const suffix = escapeTelegramQueueMenuHtml(
|
|
108
|
+
const suffix = escapeTelegramQueueMenuHtml(
|
|
109
|
+
QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX,
|
|
110
|
+
);
|
|
104
111
|
let escaped = "";
|
|
105
112
|
let truncated = false;
|
|
106
113
|
for (const char of text) {
|
|
@@ -117,24 +124,27 @@ function escapeTelegramQueueMenuHtmlPreview(text: string): string {
|
|
|
117
124
|
return truncated ? escaped + suffix : escaped;
|
|
118
125
|
}
|
|
119
126
|
function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
120
|
-
|
|
127
|
+
const badge = item.isPriority ? ` ${item.priorityEmoji ?? "⚡"}` : "";
|
|
128
|
+
const heading = `<b>${item.queuePosition}.</b>${badge}`;
|
|
129
|
+
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
130
|
+
return `${heading}\n${preview}`;
|
|
121
131
|
}
|
|
122
132
|
function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
123
133
|
chatId: number,
|
|
124
134
|
replyToMessageId: number,
|
|
125
135
|
isPriority: boolean,
|
|
126
|
-
priorityEmoji?: string,
|
|
127
136
|
): TelegramQueueMenuReplyMarkup {
|
|
128
|
-
const priorityLabel = isPriority
|
|
129
|
-
? `🐢 Deprioritize ${priorityEmoji ?? "⚡"}`
|
|
130
|
-
: "⚡ Prioritize";
|
|
131
137
|
return {
|
|
132
138
|
inline_keyboard: [
|
|
133
139
|
[{ text: "⬆️ Back", callback_data: "queue:list" }],
|
|
134
140
|
[
|
|
135
141
|
{
|
|
136
|
-
text:
|
|
137
|
-
callback_data: `queue:prio:${chatId}:${replyToMessageId}`,
|
|
142
|
+
text: isPriority ? "🟡 Priority" : "⚫️ Priority",
|
|
143
|
+
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:priority`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
text: isPriority ? "⚫️ Normal" : "🟡 Normal",
|
|
147
|
+
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:normal`,
|
|
138
148
|
},
|
|
139
149
|
],
|
|
140
150
|
[
|
|
@@ -157,8 +167,6 @@ function buildTelegramQueueDeleteConfirmationReplyMarkup(
|
|
|
157
167
|
text: "🗑 Yes, delete",
|
|
158
168
|
callback_data: `queue:confirm-delete:${chatId}:${replyToMessageId}`,
|
|
159
169
|
},
|
|
160
|
-
],
|
|
161
|
-
[
|
|
162
170
|
{
|
|
163
171
|
text: "❌ No",
|
|
164
172
|
callback_data: `queue:keep:${chatId}:${replyToMessageId}`,
|
|
@@ -174,6 +182,11 @@ interface TelegramQueueMenuCallbackDeps<Context = unknown> {
|
|
|
174
182
|
replyToMessageId: number,
|
|
175
183
|
) => TelegramQueueMenuItem | undefined;
|
|
176
184
|
togglePriority: (chatId: number, replyToMessageId: number) => boolean;
|
|
185
|
+
setPriority: (
|
|
186
|
+
chatId: number,
|
|
187
|
+
replyToMessageId: number,
|
|
188
|
+
enabled: boolean,
|
|
189
|
+
) => boolean;
|
|
177
190
|
cancelItem: (
|
|
178
191
|
chatId: number,
|
|
179
192
|
replyToMessageId: number,
|
|
@@ -204,7 +217,7 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
204
217
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
205
218
|
return true;
|
|
206
219
|
}
|
|
207
|
-
if (data === "queue:list") {
|
|
220
|
+
if (data === "queue:list" || data === "queue:refresh") {
|
|
208
221
|
await updateTelegramQueueMenuList(
|
|
209
222
|
callbackQueryId,
|
|
210
223
|
replyChatId,
|
|
@@ -225,6 +238,22 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
225
238
|
);
|
|
226
239
|
return true;
|
|
227
240
|
}
|
|
241
|
+
const prioSetMatch = data.match(
|
|
242
|
+
/^queue:prio-set:(\d+):(\d+):(priority|normal)$/,
|
|
243
|
+
);
|
|
244
|
+
if (prioSetMatch) {
|
|
245
|
+
await handleTelegramQueueMenuPrioritySet(
|
|
246
|
+
callbackQueryId,
|
|
247
|
+
replyChatId,
|
|
248
|
+
replyMessageId,
|
|
249
|
+
Number(prioSetMatch[1]),
|
|
250
|
+
Number(prioSetMatch[2]),
|
|
251
|
+
prioSetMatch[3] === "priority",
|
|
252
|
+
ctx,
|
|
253
|
+
deps,
|
|
254
|
+
);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
228
257
|
const prioMatch = data.match(/^queue:prio:(\d+):(\d+)$/);
|
|
229
258
|
if (prioMatch) {
|
|
230
259
|
await handleTelegramQueueMenuPriority(
|
|
@@ -280,7 +309,7 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
280
309
|
function getTelegramQueueMenuListText(
|
|
281
310
|
items: readonly TelegramQueueMenuItem[],
|
|
282
311
|
): string {
|
|
283
|
-
if (items.length === 0) return "<b
|
|
312
|
+
if (items.length === 0) return "<b>⌛ Queue is empty.</b>";
|
|
284
313
|
return "<b>⏳ Queue:</b>";
|
|
285
314
|
}
|
|
286
315
|
async function updateTelegramQueueMenuList<Context>(
|
|
@@ -334,12 +363,7 @@ async function handleTelegramQueueMenuPick<Context>(
|
|
|
334
363
|
replyChatId,
|
|
335
364
|
replyMessageId,
|
|
336
365
|
getTelegramQueueMenuItemText(item),
|
|
337
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
338
|
-
chatId,
|
|
339
|
-
msgId,
|
|
340
|
-
item.isPriority,
|
|
341
|
-
item.priorityEmoji,
|
|
342
|
-
),
|
|
366
|
+
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, item.isPriority),
|
|
343
367
|
);
|
|
344
368
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
345
369
|
}
|
|
@@ -361,24 +385,77 @@ async function handleTelegramQueueMenuPriority<Context>(
|
|
|
361
385
|
deps,
|
|
362
386
|
);
|
|
363
387
|
}
|
|
364
|
-
|
|
388
|
+
await updateTelegramQueueMenuPriority(
|
|
389
|
+
callbackQueryId,
|
|
390
|
+
replyChatId,
|
|
391
|
+
replyMessageId,
|
|
392
|
+
chatId,
|
|
393
|
+
msgId,
|
|
394
|
+
!item.isPriority,
|
|
395
|
+
ctx,
|
|
396
|
+
deps,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
async function handleTelegramQueueMenuPrioritySet<Context>(
|
|
400
|
+
callbackQueryId: string,
|
|
401
|
+
replyChatId: number,
|
|
402
|
+
replyMessageId: number,
|
|
403
|
+
chatId: number,
|
|
404
|
+
msgId: number,
|
|
405
|
+
enabled: boolean,
|
|
406
|
+
ctx: Context,
|
|
407
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
408
|
+
): Promise<void> {
|
|
409
|
+
const item = deps.findItem(chatId, msgId);
|
|
410
|
+
if (!item) {
|
|
411
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
412
|
+
callbackQueryId,
|
|
413
|
+
replyChatId,
|
|
414
|
+
replyMessageId,
|
|
415
|
+
deps,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
await updateTelegramQueueMenuPriority(
|
|
419
|
+
callbackQueryId,
|
|
420
|
+
replyChatId,
|
|
421
|
+
replyMessageId,
|
|
422
|
+
chatId,
|
|
423
|
+
msgId,
|
|
424
|
+
enabled,
|
|
425
|
+
ctx,
|
|
426
|
+
deps,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
async function updateTelegramQueueMenuPriority<Context>(
|
|
430
|
+
callbackQueryId: string,
|
|
431
|
+
replyChatId: number,
|
|
432
|
+
replyMessageId: number,
|
|
433
|
+
chatId: number,
|
|
434
|
+
msgId: number,
|
|
435
|
+
enabled: boolean,
|
|
436
|
+
ctx: Context,
|
|
437
|
+
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
438
|
+
): Promise<void> {
|
|
439
|
+
deps.setPriority(chatId, msgId, enabled);
|
|
365
440
|
deps.updateStatus(ctx);
|
|
366
441
|
const updated = deps.findItem(chatId, msgId);
|
|
367
|
-
|
|
442
|
+
if (!updated) {
|
|
443
|
+
return refreshStaleTelegramQueueMenuItem(
|
|
444
|
+
callbackQueryId,
|
|
445
|
+
replyChatId,
|
|
446
|
+
replyMessageId,
|
|
447
|
+
deps,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
368
450
|
await deps.updateQueueMessage(
|
|
369
451
|
replyChatId,
|
|
370
452
|
replyMessageId,
|
|
371
|
-
getTelegramQueueMenuItemText(
|
|
372
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
373
|
-
chatId,
|
|
374
|
-
msgId,
|
|
375
|
-
newPriority,
|
|
376
|
-
updated?.priorityEmoji ?? item.priorityEmoji,
|
|
377
|
-
),
|
|
453
|
+
getTelegramQueueMenuItemText(updated),
|
|
454
|
+
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, updated.isPriority),
|
|
378
455
|
);
|
|
379
456
|
await deps.answerCallbackQuery(
|
|
380
457
|
callbackQueryId,
|
|
381
|
-
|
|
458
|
+
updated.isPriority ? "Prioritized." : "Normal priority.",
|
|
382
459
|
);
|
|
383
460
|
}
|
|
384
461
|
async function handleTelegramQueueMenuDeleteRequest<Context>(
|
|
@@ -427,12 +504,7 @@ async function handleTelegramQueueMenuKeep<Context>(
|
|
|
427
504
|
replyChatId,
|
|
428
505
|
replyMessageId,
|
|
429
506
|
getTelegramQueueMenuItemText(item),
|
|
430
|
-
buildTelegramQueueItemSubmenuReplyMarkup(
|
|
431
|
-
chatId,
|
|
432
|
-
msgId,
|
|
433
|
-
item.isPriority,
|
|
434
|
-
item.priorityEmoji,
|
|
435
|
-
),
|
|
507
|
+
buildTelegramQueueItemSubmenuReplyMarkup(chatId, msgId, item.isPriority),
|
|
436
508
|
);
|
|
437
509
|
await deps.answerCallbackQuery(callbackQueryId, "Kept in queue.");
|
|
438
510
|
}
|
|
@@ -651,6 +723,12 @@ function createQueueMenuCallbackHandler<
|
|
|
651
723
|
queueMutationRuntime: deps.queueMutationRuntime,
|
|
652
724
|
});
|
|
653
725
|
},
|
|
726
|
+
setPriority: function setPriority(cId, rId, enabled) {
|
|
727
|
+
return setQueuedTelegramPromptPriority(cId, rId, enabled, ctx, {
|
|
728
|
+
getQueueSnapshot,
|
|
729
|
+
queueMutationRuntime: deps.queueMutationRuntime,
|
|
730
|
+
});
|
|
731
|
+
},
|
|
654
732
|
cancelItem: function cancelItem(cId, rId, c) {
|
|
655
733
|
return cancelQueuedTelegramItem(cId, rId, c, {
|
|
656
734
|
getQueueSnapshot,
|
|
@@ -686,6 +764,29 @@ function toggleQueuedTelegramPromptPriority<Context>(
|
|
|
686
764
|
}
|
|
687
765
|
return true;
|
|
688
766
|
}
|
|
767
|
+
function setQueuedTelegramPromptPriority<Context>(
|
|
768
|
+
chatId: number,
|
|
769
|
+
replyToMessageId: number,
|
|
770
|
+
enabled: boolean,
|
|
771
|
+
ctx: Context,
|
|
772
|
+
deps: {
|
|
773
|
+
getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
|
|
774
|
+
queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
|
|
775
|
+
},
|
|
776
|
+
): boolean {
|
|
777
|
+
const item = findTelegramQueueItem(
|
|
778
|
+
deps.getQueueSnapshot(),
|
|
779
|
+
chatId,
|
|
780
|
+
replyToMessageId,
|
|
781
|
+
);
|
|
782
|
+
if (!item) return false;
|
|
783
|
+
if (enabled) {
|
|
784
|
+
deps.queueMutationRuntime.prioritizeByMessageId(replyToMessageId, ctx);
|
|
785
|
+
} else {
|
|
786
|
+
deps.queueMutationRuntime.clearPriorityByMessageId(replyToMessageId, ctx);
|
|
787
|
+
}
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
689
790
|
function cancelQueuedTelegramItem<Context>(
|
|
690
791
|
chatId: number,
|
|
691
792
|
replyToMessageId: number,
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram settings menu UI helpers
|
|
3
|
+
* Zones: telegram ui, settings controls, menu composition
|
|
4
|
+
* Owns hidden settings-menu rendering, settings callbacks, and persisted toggle wiring
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
8
|
+
import type { TelegramModelMenuState } from "./menu-model.ts";
|
|
9
|
+
import type { MenuModel } from "./model.ts";
|
|
10
|
+
|
|
11
|
+
export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
12
|
+
|
|
13
|
+
export interface TelegramSettingsStateDeps {
|
|
14
|
+
isProactivePushEnabled: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
|
|
18
|
+
setProactivePushEnabled: (enabled: boolean) => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TelegramSettingsMenuOpenDeps<
|
|
22
|
+
TModel extends MenuModel = MenuModel,
|
|
23
|
+
> extends TelegramSettingsStateDeps {
|
|
24
|
+
getModelMenuState: () => Promise<TelegramModelMenuState<TModel>>;
|
|
25
|
+
sendSettingsMenu: (
|
|
26
|
+
state: TelegramModelMenuState<TModel>,
|
|
27
|
+
text: string,
|
|
28
|
+
replyMarkup: TelegramSettingsMenuReplyMarkup,
|
|
29
|
+
) => Promise<number | undefined>;
|
|
30
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TelegramSettingsMenuCallbackDeps extends TelegramSettingsMutationDeps {
|
|
34
|
+
updateSettingsMessage: (
|
|
35
|
+
text: string,
|
|
36
|
+
replyMarkup: TelegramSettingsMenuReplyMarkup,
|
|
37
|
+
) => Promise<void>;
|
|
38
|
+
answerCallbackQuery: (
|
|
39
|
+
callbackQueryId: string,
|
|
40
|
+
text?: string,
|
|
41
|
+
) => Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TelegramSettingsMenuRuntime<TContext> {
|
|
45
|
+
openSettingsMenu: (
|
|
46
|
+
chatId: number,
|
|
47
|
+
replyToMessageId: number,
|
|
48
|
+
ctx: TContext,
|
|
49
|
+
) => Promise<void>;
|
|
50
|
+
handleCallbackQuery: (
|
|
51
|
+
query: {
|
|
52
|
+
id: string;
|
|
53
|
+
data?: string;
|
|
54
|
+
message?: { message_id?: number };
|
|
55
|
+
},
|
|
56
|
+
ctx: TContext,
|
|
57
|
+
) => Promise<boolean>;
|
|
58
|
+
updateSettingsMenuMessage: (
|
|
59
|
+
state: TelegramModelMenuState,
|
|
60
|
+
ctx: TContext,
|
|
61
|
+
) => Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TelegramSettingsMenuMessageUpdateDeps extends TelegramSettingsStateDeps {
|
|
65
|
+
updateSettingsMessage: (
|
|
66
|
+
text: string,
|
|
67
|
+
replyMarkup: TelegramSettingsMenuReplyMarkup,
|
|
68
|
+
) => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TelegramSettingsMenuRuntimeDeps<
|
|
72
|
+
TContext,
|
|
73
|
+
TModel extends MenuModel = MenuModel,
|
|
74
|
+
> extends TelegramSettingsMutationDeps {
|
|
75
|
+
getModelMenuState: (
|
|
76
|
+
chatId: number,
|
|
77
|
+
ctx: TContext,
|
|
78
|
+
) => Promise<TelegramModelMenuState<TModel>>;
|
|
79
|
+
getStoredModelMenuState: (
|
|
80
|
+
messageId: number | undefined,
|
|
81
|
+
) => TelegramModelMenuState<TModel> | undefined;
|
|
82
|
+
storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
|
|
83
|
+
editInteractiveMessage: (
|
|
84
|
+
chatId: number,
|
|
85
|
+
messageId: number,
|
|
86
|
+
text: string,
|
|
87
|
+
mode: "html" | "plain",
|
|
88
|
+
replyMarkup: TelegramSettingsMenuReplyMarkup,
|
|
89
|
+
) => Promise<void>;
|
|
90
|
+
sendInteractiveMessage: (
|
|
91
|
+
chatId: number,
|
|
92
|
+
text: string,
|
|
93
|
+
mode: "html" | "plain",
|
|
94
|
+
replyMarkup: TelegramSettingsMenuReplyMarkup,
|
|
95
|
+
) => Promise<number | undefined>;
|
|
96
|
+
answerCallbackQuery: (
|
|
97
|
+
callbackQueryId: string,
|
|
98
|
+
text?: string,
|
|
99
|
+
) => Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
103
|
+
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>Proactive push:</b>";
|
|
104
|
+
|
|
105
|
+
export function buildTelegramSettingsMenuText(): string {
|
|
106
|
+
return SETTINGS_MENU_TITLE;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildProactivePushSettingsText(): string {
|
|
110
|
+
return [
|
|
111
|
+
PROACTIVE_PUSH_SETTINGS_TITLE,
|
|
112
|
+
"",
|
|
113
|
+
"Send successful local π task results to Telegram when the bridge is connected.",
|
|
114
|
+
"Default: off. Persists until disabled or removed from config.",
|
|
115
|
+
].join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildTelegramSettingsMenuReplyMarkup(
|
|
119
|
+
proactivePushEnabled: boolean,
|
|
120
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
121
|
+
return {
|
|
122
|
+
inline_keyboard: [
|
|
123
|
+
[{ text: "⬆️ Main menu", callback_data: "menu:back" }],
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
text: `${proactivePushEnabled ? "🟢" : "⚫️"} Proactive push`,
|
|
127
|
+
callback_data: "settings:open:proactive",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function openTelegramSettingsMenu<
|
|
135
|
+
TModel extends MenuModel = MenuModel,
|
|
136
|
+
>(deps: TelegramSettingsMenuOpenDeps<TModel>): Promise<void> {
|
|
137
|
+
const state = await deps.getModelMenuState();
|
|
138
|
+
const messageId = await deps.sendSettingsMenu(
|
|
139
|
+
state,
|
|
140
|
+
buildTelegramSettingsMenuText(),
|
|
141
|
+
buildTelegramSettingsMenuReplyMarkup(deps.isProactivePushEnabled()),
|
|
142
|
+
);
|
|
143
|
+
if (messageId === undefined) return;
|
|
144
|
+
state.messageId = messageId;
|
|
145
|
+
state.mode = "settings";
|
|
146
|
+
deps.storeModelMenuState(state);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function buildProactivePushSettingsReplyMarkup(
|
|
150
|
+
proactivePushEnabled: boolean,
|
|
151
|
+
): TelegramSettingsMenuReplyMarkup {
|
|
152
|
+
return {
|
|
153
|
+
inline_keyboard: [
|
|
154
|
+
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
text: proactivePushEnabled ? "🟢 On" : "⚫️ On",
|
|
158
|
+
callback_data: "settings:set:proactive:on",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
text: proactivePushEnabled ? "⚫️ Off" : "🟡 Off",
|
|
162
|
+
callback_data: "settings:set:proactive:off",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function updateTelegramSettingsMenuMessage(
|
|
170
|
+
deps: TelegramSettingsMenuMessageUpdateDeps,
|
|
171
|
+
): Promise<void> {
|
|
172
|
+
await deps.updateSettingsMessage(
|
|
173
|
+
buildTelegramSettingsMenuText(),
|
|
174
|
+
buildTelegramSettingsMenuReplyMarkup(deps.isProactivePushEnabled()),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function updateProactivePushSettingsMessage(
|
|
179
|
+
deps: TelegramSettingsMenuCallbackDeps,
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
await deps.updateSettingsMessage(
|
|
182
|
+
buildProactivePushSettingsText(),
|
|
183
|
+
buildProactivePushSettingsReplyMarkup(deps.isProactivePushEnabled()),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function handleTelegramSettingsMenuCallbackAction(
|
|
188
|
+
callbackQueryId: string,
|
|
189
|
+
data: string | undefined,
|
|
190
|
+
deps: TelegramSettingsMenuCallbackDeps,
|
|
191
|
+
): Promise<boolean> {
|
|
192
|
+
if (!data?.startsWith("settings:")) return false;
|
|
193
|
+
if (data === "settings:list") {
|
|
194
|
+
await updateTelegramSettingsMenuMessage(deps);
|
|
195
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (data === "settings:open:proactive") {
|
|
199
|
+
await updateProactivePushSettingsMessage(deps);
|
|
200
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
data === "settings:set:proactive:on" ||
|
|
205
|
+
data === "settings:set:proactive:off"
|
|
206
|
+
) {
|
|
207
|
+
const enabled = data.endsWith(":on");
|
|
208
|
+
await deps.setProactivePushEnabled(enabled);
|
|
209
|
+
await updateProactivePushSettingsMessage(deps);
|
|
210
|
+
await deps.answerCallbackQuery(
|
|
211
|
+
callbackQueryId,
|
|
212
|
+
`Proactive push ${enabled ? "enabled" : "disabled"}`,
|
|
213
|
+
);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function createTelegramSettingsMenuRuntime<
|
|
221
|
+
TContext,
|
|
222
|
+
TModel extends MenuModel = MenuModel,
|
|
223
|
+
>(
|
|
224
|
+
deps: TelegramSettingsMenuRuntimeDeps<TContext, TModel>,
|
|
225
|
+
): TelegramSettingsMenuRuntime<TContext> {
|
|
226
|
+
return {
|
|
227
|
+
openSettingsMenu: (chatId, _replyToMessageId, ctx) =>
|
|
228
|
+
openTelegramSettingsMenu({
|
|
229
|
+
getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
|
|
230
|
+
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
231
|
+
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
232
|
+
deps.sendInteractiveMessage(state.chatId, text, "html", replyMarkup),
|
|
233
|
+
storeModelMenuState: deps.storeModelMenuState,
|
|
234
|
+
}),
|
|
235
|
+
updateSettingsMenuMessage: (state) =>
|
|
236
|
+
updateTelegramSettingsMenuMessage({
|
|
237
|
+
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
238
|
+
updateSettingsMessage: (text, replyMarkup) =>
|
|
239
|
+
deps.editInteractiveMessage(
|
|
240
|
+
state.chatId,
|
|
241
|
+
state.messageId,
|
|
242
|
+
text,
|
|
243
|
+
"html",
|
|
244
|
+
replyMarkup,
|
|
245
|
+
),
|
|
246
|
+
}),
|
|
247
|
+
handleCallbackQuery: async (query) => {
|
|
248
|
+
if (!query.data?.startsWith("settings:")) return false;
|
|
249
|
+
const state = deps.getStoredModelMenuState(query.message?.message_id);
|
|
250
|
+
if (!state) {
|
|
251
|
+
await deps.answerCallbackQuery(
|
|
252
|
+
query.id,
|
|
253
|
+
"Interactive message expired.",
|
|
254
|
+
);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
return handleTelegramSettingsMenuCallbackAction(query.id, query.data, {
|
|
258
|
+
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
259
|
+
setProactivePushEnabled: deps.setProactivePushEnabled,
|
|
260
|
+
updateSettingsMessage: (text, replyMarkup) =>
|
|
261
|
+
deps.editInteractiveMessage(
|
|
262
|
+
state.chatId,
|
|
263
|
+
state.messageId,
|
|
264
|
+
text,
|
|
265
|
+
"html",
|
|
266
|
+
replyMarkup,
|
|
267
|
+
),
|
|
268
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
package/lib/menu-status.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
export interface TelegramStatusMenuCallbackDeps {
|
|
22
22
|
updateModelMenuMessage: () => Promise<void>;
|
|
23
23
|
updateThinkingMenuMessage: () => Promise<void>;
|
|
24
|
+
updateSettingsMenuMessage?: () => Promise<void>;
|
|
24
25
|
answerCallbackQuery: (
|
|
25
26
|
callbackQueryId: string,
|
|
26
27
|
text?: string,
|
|
@@ -49,7 +50,7 @@ export interface TelegramStatusMenuOpenDeps<
|
|
|
49
50
|
|
|
50
51
|
function isTelegramStatusMenuCallbackAction(
|
|
51
52
|
data: string | undefined,
|
|
52
|
-
action: "model" | "thinking",
|
|
53
|
+
action: "model" | "thinking" | "settings",
|
|
53
54
|
): boolean {
|
|
54
55
|
return data === `menu:${action}` || data === `status:${action}`;
|
|
55
56
|
}
|
|
@@ -119,6 +120,12 @@ export async function handleTelegramStatusMenuCallbackAction(
|
|
|
119
120
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
120
121
|
return true;
|
|
121
122
|
}
|
|
123
|
+
if (isTelegramStatusMenuCallbackAction(data, "settings")) {
|
|
124
|
+
if (!deps.updateSettingsMenuMessage) return false;
|
|
125
|
+
await deps.updateSettingsMenuMessage();
|
|
126
|
+
await deps.answerCallbackQuery(callbackQueryId);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
122
129
|
if (!isTelegramStatusMenuCallbackAction(data, "thinking")) return false;
|
|
123
130
|
if (!activeModel?.reasoning) {
|
|
124
131
|
await deps.answerCallbackQuery(
|
|
@@ -160,10 +167,16 @@ export function buildStatusReplyMarkup(
|
|
|
160
167
|
}
|
|
161
168
|
rows.push([
|
|
162
169
|
{
|
|
163
|
-
text:
|
|
170
|
+
text: `${queueItemCount === 0 ? "⌛" : "⏳"} Queue: ${queueItemCount}`,
|
|
164
171
|
callback_data: "menu:queue",
|
|
165
172
|
},
|
|
166
173
|
]);
|
|
174
|
+
rows.push([
|
|
175
|
+
{
|
|
176
|
+
text: "⚙️ Settings",
|
|
177
|
+
callback_data: "menu:settings",
|
|
178
|
+
},
|
|
179
|
+
]);
|
|
167
180
|
return { inline_keyboard: rows };
|
|
168
181
|
}
|
|
169
182
|
|
package/lib/menu-thinking.ts
CHANGED
|
@@ -109,7 +109,7 @@ export async function handleTelegramThinkingMenuCallbackAction(
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export function buildThinkingMenuText(): string {
|
|
112
|
-
return "<b
|
|
112
|
+
return "<b>🧠 Choose a thinking level:</b>";
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function buildThinkingMenuReplyMarkup(
|
|
@@ -119,7 +119,7 @@ export function buildThinkingMenuReplyMarkup(
|
|
|
119
119
|
rows.push(
|
|
120
120
|
...THINKING_LEVELS.map((level) => [
|
|
121
121
|
{
|
|
122
|
-
text: level === currentThinkingLevel ?
|
|
122
|
+
text: level === currentThinkingLevel ? `🟢 ${level}` : level,
|
|
123
123
|
callback_data: `thinking:set:${level}`,
|
|
124
124
|
},
|
|
125
125
|
]),
|