@llblab/pi-telegram 0.8.2 → 0.9.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 +1 -0
- package/CHANGELOG.md +19 -1
- package/README.md +10 -7
- package/docs/architecture.md +34 -31
- package/index.ts +38 -2
- package/lib/commands.ts +72 -0
- package/lib/config.ts +24 -0
- package/lib/menu-model.ts +302 -18
- 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 +1 -1
- package/lib/menu.ts +45 -3
- package/lib/pi.ts +16 -0
- package/lib/prompts.ts +24 -1
- package/lib/queue.ts +37 -14
- package/lib/routing.ts +26 -0
- package/lib/status.ts +20 -6
- package/lib/updates.ts +2 -0
- package/package.json +1 -1
package/lib/menu-model.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
8
8
|
import {
|
|
9
|
+
getCanonicalModelId,
|
|
9
10
|
type MenuModel,
|
|
10
11
|
modelsMatch,
|
|
11
12
|
parseTelegramCliScopedModelPatterns,
|
|
@@ -29,7 +30,18 @@ export interface TelegramModelMenuState<TModel extends MenuModel = MenuModel> {
|
|
|
29
30
|
scopedModels: ScopedTelegramModel<TModel>[];
|
|
30
31
|
allModels: ScopedTelegramModel<TModel>[];
|
|
31
32
|
note?: string;
|
|
32
|
-
|
|
33
|
+
selectedModelIndex?: number;
|
|
34
|
+
selectedModelKey?: string;
|
|
35
|
+
scopedModelPatterns?: string[];
|
|
36
|
+
canMutateScope?: boolean;
|
|
37
|
+
mode:
|
|
38
|
+
| "status"
|
|
39
|
+
| "model"
|
|
40
|
+
| "model-pages"
|
|
41
|
+
| "model-detail"
|
|
42
|
+
| "thinking"
|
|
43
|
+
| "queue"
|
|
44
|
+
| "settings";
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
export interface StoredTelegramModelMenuState<
|
|
@@ -90,7 +102,9 @@ export interface TelegramModelMenuRuntimeOptions<
|
|
|
90
102
|
|
|
91
103
|
export interface MenuSettingsManager {
|
|
92
104
|
reload: () => Promise<void>;
|
|
105
|
+
flush?: () => Promise<void>;
|
|
93
106
|
getEnabledModels: () => string[] | undefined;
|
|
107
|
+
setEnabledModels?: (patterns: string[] | undefined) => void;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
export type TelegramModelMenuStateBuilderContext<
|
|
@@ -134,6 +148,7 @@ export type TelegramModelMenuCallbackDeps<
|
|
|
134
148
|
) => Promise<void>;
|
|
135
149
|
updateModelMenuMessage: () => Promise<void>;
|
|
136
150
|
updateStatusMessage: () => Promise<void>;
|
|
151
|
+
persistScopedModelPatterns?: (patterns: string[]) => Promise<void>;
|
|
137
152
|
setModel: (model: TModel) => Promise<boolean>;
|
|
138
153
|
setCurrentModel: (model: TModel) => void;
|
|
139
154
|
setThinkingLevel: (level: ThinkingLevel) => void;
|
|
@@ -193,6 +208,7 @@ export type TelegramModelCallbackPlan<TModel extends MenuModel = MenuModel> =
|
|
|
193
208
|
| { kind: "ignore" }
|
|
194
209
|
| { kind: "answer"; text?: string }
|
|
195
210
|
| { kind: "update-menu"; text?: string }
|
|
211
|
+
| { kind: "persist-scope"; patterns: string[]; text: string }
|
|
196
212
|
| {
|
|
197
213
|
kind: "refresh-status";
|
|
198
214
|
selection: ScopedTelegramModel<TModel>;
|
|
@@ -226,6 +242,7 @@ export interface TelegramModelMenuRuntime<
|
|
|
226
242
|
messageId: number | undefined,
|
|
227
243
|
) => TelegramModelMenuState<TModel> | undefined;
|
|
228
244
|
clear: () => void;
|
|
245
|
+
clearCachedInputs: () => void;
|
|
229
246
|
buildState: <TContext extends TelegramModelMenuRuntimeContext<TModel>>(
|
|
230
247
|
options: Omit<
|
|
231
248
|
TelegramModelMenuRuntimeOptions<TContext, TModel>,
|
|
@@ -238,6 +255,7 @@ export const TELEGRAM_MODEL_PAGE_SIZE = 6;
|
|
|
238
255
|
const TELEGRAM_MODEL_PAGE_PICKER_ROW_SIZE = 4;
|
|
239
256
|
export const MODEL_MENU_TITLE = "<b>🤖 Choose a model:</b>";
|
|
240
257
|
export const MODEL_PAGE_MENU_TITLE = "<b>Choose a page:</b>";
|
|
258
|
+
export const MODEL_DETAIL_MENU_TITLE = "<b>🤖 Model:</b>";
|
|
241
259
|
|
|
242
260
|
function truncateTelegramButtonLabel(label: string, maxLength = 56): string {
|
|
243
261
|
return label.length <= maxLength
|
|
@@ -249,10 +267,21 @@ function getTelegramCliScopedModelPatterns(): string[] | undefined {
|
|
|
249
267
|
return parseTelegramCliScopedModelPatterns(process.argv.slice(2));
|
|
250
268
|
}
|
|
251
269
|
|
|
252
|
-
function parseTelegramModelMenuCallbackAction(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
270
|
+
function parseTelegramModelMenuCallbackAction(data: string | undefined):
|
|
271
|
+
| {
|
|
272
|
+
action:
|
|
273
|
+
| "noop"
|
|
274
|
+
| "scope"
|
|
275
|
+
| "page"
|
|
276
|
+
| "pages"
|
|
277
|
+
| "open"
|
|
278
|
+
| "pick"
|
|
279
|
+
| "pick-selected"
|
|
280
|
+
| "scope-enable"
|
|
281
|
+
| "scope-disable"
|
|
282
|
+
| "scope-toggle";
|
|
283
|
+
value?: string;
|
|
284
|
+
}
|
|
256
285
|
| undefined {
|
|
257
286
|
if (!data?.startsWith("model:")) return undefined;
|
|
258
287
|
const [, action, value] = data.split(":");
|
|
@@ -261,7 +290,12 @@ function parseTelegramModelMenuCallbackAction(
|
|
|
261
290
|
action === "scope" ||
|
|
262
291
|
action === "page" ||
|
|
263
292
|
action === "pages" ||
|
|
264
|
-
action === "
|
|
293
|
+
action === "open" ||
|
|
294
|
+
action === "pick" ||
|
|
295
|
+
action === "pick-selected" ||
|
|
296
|
+
action === "scope-enable" ||
|
|
297
|
+
action === "scope-disable" ||
|
|
298
|
+
action === "scope-toggle"
|
|
265
299
|
) {
|
|
266
300
|
return { action, value };
|
|
267
301
|
}
|
|
@@ -311,7 +345,7 @@ export function formatScopedModelButtonText<
|
|
|
311
345
|
entry: ScopedTelegramModel<TModel>,
|
|
312
346
|
currentModel: TModel | undefined,
|
|
313
347
|
): string {
|
|
314
|
-
let label = `${modelsMatch(entry.model, currentModel) ? "
|
|
348
|
+
let label = `${modelsMatch(entry.model, currentModel) ? "🟢 " : ""}${entry.model.id} [${entry.model.provider}]`;
|
|
315
349
|
if (entry.thinkingLevel) {
|
|
316
350
|
label += ` · ${entry.thinkingLevel}`;
|
|
317
351
|
}
|
|
@@ -401,6 +435,9 @@ export function createTelegramModelMenuRuntime<
|
|
|
401
435
|
menus.clear();
|
|
402
436
|
cachedInputs = undefined;
|
|
403
437
|
},
|
|
438
|
+
clearCachedInputs: () => {
|
|
439
|
+
cachedInputs = undefined;
|
|
440
|
+
},
|
|
404
441
|
buildState: async (stateOptions) => {
|
|
405
442
|
const result = await buildTelegramModelMenuStateRuntime({
|
|
406
443
|
...stateOptions,
|
|
@@ -490,6 +527,9 @@ export function buildTelegramModelMenuState<
|
|
|
490
527
|
scopedModels,
|
|
491
528
|
allModels,
|
|
492
529
|
note,
|
|
530
|
+
selectedModelIndex: undefined,
|
|
531
|
+
scopedModelPatterns: params.configuredScopedModelPatterns,
|
|
532
|
+
canMutateScope: !params.cliScopedModelPatterns,
|
|
493
533
|
mode: "status",
|
|
494
534
|
};
|
|
495
535
|
}
|
|
@@ -563,6 +603,128 @@ export function getTelegramModelSelection<TModel extends MenuModel = MenuModel>(
|
|
|
563
603
|
return { kind: "selected", selection };
|
|
564
604
|
}
|
|
565
605
|
|
|
606
|
+
export function applyTelegramModelDetailSelection(
|
|
607
|
+
state: TelegramModelMenuState,
|
|
608
|
+
value: string | undefined,
|
|
609
|
+
): TelegramMenuMutationResult {
|
|
610
|
+
const index = Number(value);
|
|
611
|
+
if (!Number.isFinite(index)) return "invalid";
|
|
612
|
+
const selection = getModelMenuItems(state)[index];
|
|
613
|
+
if (!selection) return "invalid";
|
|
614
|
+
state.selectedModelIndex = index;
|
|
615
|
+
state.selectedModelKey = getCanonicalModelId(selection.model);
|
|
616
|
+
state.mode = "model-detail";
|
|
617
|
+
return "changed";
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function getTelegramSelectedDetailModel<
|
|
621
|
+
TModel extends MenuModel = MenuModel,
|
|
622
|
+
>(state: TelegramModelMenuState<TModel>): TelegramMenuSelectionResult<TModel> {
|
|
623
|
+
const indexedSelection = getTelegramModelSelection(
|
|
624
|
+
state,
|
|
625
|
+
state.selectedModelIndex?.toString(),
|
|
626
|
+
);
|
|
627
|
+
if (indexedSelection.kind === "selected") {
|
|
628
|
+
if (!state.selectedModelKey) return indexedSelection;
|
|
629
|
+
const indexedKey = getCanonicalModelId(
|
|
630
|
+
indexedSelection.selection.model,
|
|
631
|
+
).toLowerCase();
|
|
632
|
+
if (indexedKey === state.selectedModelKey.toLowerCase()) {
|
|
633
|
+
return indexedSelection;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (state.selectedModelKey) {
|
|
637
|
+
const lowerKey = state.selectedModelKey.toLowerCase();
|
|
638
|
+
const selection = state.allModels.find(
|
|
639
|
+
(entry) => getCanonicalModelId(entry.model).toLowerCase() === lowerKey,
|
|
640
|
+
);
|
|
641
|
+
if (selection) return { kind: "selected", selection };
|
|
642
|
+
}
|
|
643
|
+
return indexedSelection;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function isTelegramModelScoped(
|
|
647
|
+
state: TelegramModelMenuState,
|
|
648
|
+
model: MenuModel,
|
|
649
|
+
): boolean {
|
|
650
|
+
const key = getCanonicalModelId(model);
|
|
651
|
+
return state.scopedModels.some(
|
|
652
|
+
(entry) => getCanonicalModelId(entry.model) === key,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function focusTelegramModelListPage(
|
|
657
|
+
state: TelegramModelMenuState,
|
|
658
|
+
model: MenuModel,
|
|
659
|
+
pageSize = TELEGRAM_MODEL_PAGE_SIZE,
|
|
660
|
+
): void {
|
|
661
|
+
const key = getCanonicalModelId(model).toLowerCase();
|
|
662
|
+
const index = getModelMenuItems(state).findIndex(
|
|
663
|
+
(entry) => getCanonicalModelId(entry.model).toLowerCase() === key,
|
|
664
|
+
);
|
|
665
|
+
state.page = index < 0 ? 0 : Math.floor(index / pageSize);
|
|
666
|
+
state.mode = "model";
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function formatScopedModelPattern(entry: ScopedTelegramModel): string {
|
|
670
|
+
const key = getCanonicalModelId(entry.model);
|
|
671
|
+
return entry.thinkingLevel ? `${key}:${entry.thinkingLevel}` : key;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function setTelegramModelScope(
|
|
675
|
+
state: TelegramModelMenuState,
|
|
676
|
+
model: MenuModel,
|
|
677
|
+
enabled: boolean,
|
|
678
|
+
): { patterns: string[]; enabled: boolean } {
|
|
679
|
+
const key = getCanonicalModelId(model);
|
|
680
|
+
const lowerKey = key.toLowerCase();
|
|
681
|
+
const scopedModelPatterns = state.scopedModelPatterns ?? [];
|
|
682
|
+
const allModels = state.allModels.map((entry) => entry.model);
|
|
683
|
+
const patterns: string[] = [];
|
|
684
|
+
for (const pattern of scopedModelPatterns) {
|
|
685
|
+
const resolved = resolveScopedModelPatterns([pattern], allModels);
|
|
686
|
+
const matchesModel = resolved.some(
|
|
687
|
+
(entry) => getCanonicalModelId(entry.model).toLowerCase() === lowerKey,
|
|
688
|
+
);
|
|
689
|
+
if (enabled || !matchesModel) {
|
|
690
|
+
if (pattern.toLowerCase() !== lowerKey) patterns.push(pattern);
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
for (const entry of resolved) {
|
|
694
|
+
if (getCanonicalModelId(entry.model).toLowerCase() === lowerKey) continue;
|
|
695
|
+
const expandedPattern = formatScopedModelPattern(entry);
|
|
696
|
+
const duplicated = patterns.some(
|
|
697
|
+
(item) => item.toLowerCase() === expandedPattern.toLowerCase(),
|
|
698
|
+
);
|
|
699
|
+
if (!duplicated) patterns.push(expandedPattern);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (enabled) patterns.push(key);
|
|
703
|
+
state.scopedModelPatterns = patterns;
|
|
704
|
+
state.scopedModels = sortScopedModels(
|
|
705
|
+
resolveScopedModelPatterns(
|
|
706
|
+
patterns,
|
|
707
|
+
state.allModels.map((entry) => entry.model),
|
|
708
|
+
),
|
|
709
|
+
model,
|
|
710
|
+
);
|
|
711
|
+
if (state.scope === "scoped" && state.scopedModels.length === 0) {
|
|
712
|
+
state.scope = "all";
|
|
713
|
+
}
|
|
714
|
+
return { patterns, enabled };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function toggleTelegramModelScope(
|
|
718
|
+
state: TelegramModelMenuState,
|
|
719
|
+
model: MenuModel,
|
|
720
|
+
): { patterns: string[]; enabled: boolean } {
|
|
721
|
+
return setTelegramModelScope(
|
|
722
|
+
state,
|
|
723
|
+
model,
|
|
724
|
+
!isTelegramModelScoped(state, model),
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
566
728
|
export function buildTelegramModelCallbackPlan<
|
|
567
729
|
TModel extends MenuModel = MenuModel,
|
|
568
730
|
>(
|
|
@@ -609,10 +771,55 @@ export function buildTelegramModelCallbackPlan<
|
|
|
609
771
|
}
|
|
610
772
|
return { kind: "update-menu" };
|
|
611
773
|
}
|
|
612
|
-
if (action.action
|
|
774
|
+
if (action.action === "open") {
|
|
775
|
+
const detailResult = applyTelegramModelDetailSelection(
|
|
776
|
+
params.state,
|
|
777
|
+
action.value,
|
|
778
|
+
);
|
|
779
|
+
if (detailResult === "invalid") {
|
|
780
|
+
return { kind: "answer", text: "Selected model is no longer available." };
|
|
781
|
+
}
|
|
782
|
+
return { kind: "update-menu" };
|
|
783
|
+
}
|
|
784
|
+
if (
|
|
785
|
+
action.action === "scope-toggle" ||
|
|
786
|
+
action.action === "scope-enable" ||
|
|
787
|
+
action.action === "scope-disable"
|
|
788
|
+
) {
|
|
789
|
+
if (params.state.canMutateScope === false) {
|
|
790
|
+
return {
|
|
791
|
+
kind: "answer",
|
|
792
|
+
text: "Model scope is controlled by CLI --models.",
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
const selectionResult = getTelegramSelectedDetailModel(params.state);
|
|
796
|
+
if (selectionResult.kind !== "selected") {
|
|
797
|
+
return { kind: "answer", text: "Selected model is no longer available." };
|
|
798
|
+
}
|
|
799
|
+
const model = selectionResult.selection.model;
|
|
800
|
+
const enabled =
|
|
801
|
+
action.action === "scope-toggle"
|
|
802
|
+
? !isTelegramModelScoped(params.state, model)
|
|
803
|
+
: action.action === "scope-enable";
|
|
804
|
+
if (enabled === isTelegramModelScoped(params.state, model)) {
|
|
805
|
+
return { kind: "answer" };
|
|
806
|
+
}
|
|
807
|
+
const result = setTelegramModelScope(params.state, model, enabled);
|
|
808
|
+
return {
|
|
809
|
+
kind: "persist-scope",
|
|
810
|
+
patterns: result.patterns,
|
|
811
|
+
text: result.enabled
|
|
812
|
+
? "Added to scoped models"
|
|
813
|
+
: "Removed from scoped models",
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
if (action.action !== "pick" && action.action !== "pick-selected") {
|
|
613
817
|
return { kind: "answer" };
|
|
614
818
|
}
|
|
615
|
-
const selectionResult =
|
|
819
|
+
const selectionResult =
|
|
820
|
+
action.action === "pick-selected"
|
|
821
|
+
? getTelegramSelectedDetailModel(params.state)
|
|
822
|
+
: getTelegramModelSelection(params.state, action.value);
|
|
616
823
|
if (selectionResult.kind === "invalid") {
|
|
617
824
|
return { kind: "answer", text: "Invalid model selection." };
|
|
618
825
|
}
|
|
@@ -621,6 +828,9 @@ export function buildTelegramModelCallbackPlan<
|
|
|
621
828
|
}
|
|
622
829
|
const selection = selectionResult.selection;
|
|
623
830
|
if (modelsMatch(selection.model, params.activeModel)) {
|
|
831
|
+
if (action.action === "pick-selected") {
|
|
832
|
+
focusTelegramModelListPage(params.state, selection.model);
|
|
833
|
+
}
|
|
624
834
|
return {
|
|
625
835
|
kind: "refresh-status",
|
|
626
836
|
selection,
|
|
@@ -693,11 +903,24 @@ export async function handleTelegramModelMenuCallbackAction<
|
|
|
693
903
|
await deps.answerCallbackQuery(callbackQueryId, plan.text);
|
|
694
904
|
return true;
|
|
695
905
|
}
|
|
906
|
+
if (plan.kind === "persist-scope") {
|
|
907
|
+
if (!deps.persistScopedModelPatterns) {
|
|
908
|
+
await deps.answerCallbackQuery(
|
|
909
|
+
callbackQueryId,
|
|
910
|
+
"Scoped model persistence is unavailable.",
|
|
911
|
+
);
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
await deps.persistScopedModelPatterns(plan.patterns);
|
|
915
|
+
await deps.updateModelMenuMessage();
|
|
916
|
+
await deps.answerCallbackQuery(callbackQueryId, plan.text);
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
696
919
|
if (plan.kind === "refresh-status") {
|
|
697
920
|
if (plan.shouldApplyThinkingLevel && plan.selection.thinkingLevel) {
|
|
698
921
|
deps.setThinkingLevel(plan.selection.thinkingLevel);
|
|
699
922
|
}
|
|
700
|
-
await deps.
|
|
923
|
+
await deps.updateModelMenuMessage();
|
|
701
924
|
await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
|
|
702
925
|
return true;
|
|
703
926
|
}
|
|
@@ -710,7 +933,7 @@ export async function handleTelegramModelMenuCallbackAction<
|
|
|
710
933
|
if (plan.selection.thinkingLevel) {
|
|
711
934
|
deps.setThinkingLevel(plan.selection.thinkingLevel);
|
|
712
935
|
}
|
|
713
|
-
await deps.
|
|
936
|
+
await deps.updateModelMenuMessage();
|
|
714
937
|
if (plan.mode === "restart-after-tool") {
|
|
715
938
|
deps.stagePendingModelSwitch(plan.selection);
|
|
716
939
|
await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
|
|
@@ -756,11 +979,11 @@ export function buildModelMenuReplyMarkup(
|
|
|
756
979
|
if (state.scopedModels.length > 0) {
|
|
757
980
|
rows.push([
|
|
758
981
|
{
|
|
759
|
-
text: state.scope === "scoped" ? "
|
|
982
|
+
text: state.scope === "scoped" ? "🟡 Scoped" : "⚫️ Scoped",
|
|
760
983
|
callback_data: "model:scope:scoped",
|
|
761
984
|
},
|
|
762
985
|
{
|
|
763
|
-
text: state.scope === "all" ? "
|
|
986
|
+
text: state.scope === "all" ? "🟡 All" : "⚫️ All",
|
|
764
987
|
callback_data: "model:scope:all",
|
|
765
988
|
},
|
|
766
989
|
]);
|
|
@@ -790,13 +1013,60 @@ export function buildModelMenuReplyMarkup(
|
|
|
790
1013
|
...menuPage.items.map((entry, index) => [
|
|
791
1014
|
{
|
|
792
1015
|
text: formatScopedModelButtonText(entry, currentModel),
|
|
793
|
-
callback_data: `model:
|
|
1016
|
+
callback_data: `model:open:${menuPage.start + index}`,
|
|
794
1017
|
},
|
|
795
1018
|
]),
|
|
796
1019
|
);
|
|
797
1020
|
return { inline_keyboard: rows };
|
|
798
1021
|
}
|
|
799
1022
|
|
|
1023
|
+
export function buildModelDetailMenuReplyMarkup(
|
|
1024
|
+
state: TelegramModelMenuState,
|
|
1025
|
+
currentModel: MenuModel | undefined,
|
|
1026
|
+
): TelegramReplyMarkup {
|
|
1027
|
+
const selection = getTelegramSelectedDetailModel(state);
|
|
1028
|
+
if (selection.kind !== "selected") {
|
|
1029
|
+
return {
|
|
1030
|
+
inline_keyboard: [
|
|
1031
|
+
[{ text: "⬆️ Back", callback_data: "model:pages:back" }],
|
|
1032
|
+
],
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
const model = selection.selection.model;
|
|
1036
|
+
const active = modelsMatch(model, currentModel);
|
|
1037
|
+
const scoped = isTelegramModelScoped(state, model);
|
|
1038
|
+
return {
|
|
1039
|
+
inline_keyboard: [
|
|
1040
|
+
[{ text: "⬆️ Back", callback_data: "model:pages:back" }],
|
|
1041
|
+
[
|
|
1042
|
+
{
|
|
1043
|
+
text: active ? "🟢 Active" : "☑️ Activate",
|
|
1044
|
+
callback_data: "model:pick-selected",
|
|
1045
|
+
},
|
|
1046
|
+
],
|
|
1047
|
+
[
|
|
1048
|
+
{
|
|
1049
|
+
text: scoped ? "🟡 Scoped" : "⚫️ Scoped",
|
|
1050
|
+
callback_data: "model:scope-enable",
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
text: scoped ? "⚫️ All" : "🟡 All",
|
|
1054
|
+
callback_data: "model:scope-disable",
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
],
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export function buildModelDetailMenuText(
|
|
1062
|
+
state: TelegramModelMenuState,
|
|
1063
|
+
): string {
|
|
1064
|
+
const selection = getTelegramSelectedDetailModel(state);
|
|
1065
|
+
if (selection.kind !== "selected") return MODEL_DETAIL_MENU_TITLE;
|
|
1066
|
+
const model = selection.selection.model;
|
|
1067
|
+
return `${MODEL_DETAIL_MENU_TITLE}\n${getCanonicalModelId(model)}`;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
800
1070
|
export function buildModelPageMenuReplyMarkup(
|
|
801
1071
|
state: TelegramModelMenuState,
|
|
802
1072
|
pageSize: number,
|
|
@@ -816,10 +1086,16 @@ export function buildModelPageMenuReplyMarkup(
|
|
|
816
1086
|
menuPage.pageCount - page,
|
|
817
1087
|
),
|
|
818
1088
|
},
|
|
819
|
-
(_unused, offset) =>
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1089
|
+
(_unused, offset) => {
|
|
1090
|
+
const pageIndex = page + offset;
|
|
1091
|
+
return {
|
|
1092
|
+
text:
|
|
1093
|
+
pageIndex === menuPage.page
|
|
1094
|
+
? `🟢 ${pageIndex + 1}`
|
|
1095
|
+
: String(pageIndex + 1),
|
|
1096
|
+
callback_data: `model:page:${pageIndex}`,
|
|
1097
|
+
};
|
|
1098
|
+
},
|
|
823
1099
|
),
|
|
824
1100
|
);
|
|
825
1101
|
}
|
|
@@ -844,6 +1120,14 @@ export function buildTelegramModelMenuRenderPayload(
|
|
|
844
1120
|
if (state.mode === "model-pages") {
|
|
845
1121
|
return buildTelegramModelPageMenuRenderPayload(state);
|
|
846
1122
|
}
|
|
1123
|
+
if (state.mode === "model-detail") {
|
|
1124
|
+
return {
|
|
1125
|
+
nextMode: "model-detail",
|
|
1126
|
+
text: buildModelDetailMenuText(state),
|
|
1127
|
+
mode: "html",
|
|
1128
|
+
replyMarkup: buildModelDetailMenuReplyMarkup(state, activeModel),
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
847
1131
|
return {
|
|
848
1132
|
nextMode: "model",
|
|
849
1133
|
text: MODEL_MENU_TITLE,
|