@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/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) return { inline_keyboard: [backRow] };
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(QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX);
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
- return `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
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: priorityLabel,
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>⏳ Queue is empty.</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
- deps.togglePriority(chatId, msgId);
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
- const newPriority = updated?.isPriority ?? !item.isPriority;
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(item),
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
- newPriority ? "Prioritized." : "Deprioritized.",
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
+ }
@@ -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: `⏳ Queue: ${queueItemCount}`,
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
 
@@ -109,7 +109,7 @@ export async function handleTelegramThinkingMenuCallbackAction(
109
109
  }
110
110
 
111
111
  export function buildThinkingMenuText(): string {
112
- return "<b>Choose a thinking level:</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 ? `✅ ${level}` : level,
122
+ text: level === currentThinkingLevel ? `🟢 ${level}` : level,
123
123
  callback_data: `thinking:set:${level}`,
124
124
  },
125
125
  ]),