/** * Telegram settings menu UI helpers * Zones: telegram ui, settings controls, menu composition * Owns hidden settings-menu rendering, settings callbacks, and persisted toggle wiring */ import { getTelegramExtensionSettingsRows, type TelegramSectionRegistry, } from "./sections.ts"; import type { TelegramAssistantRenderingMode, TelegramTimeMode, } from "./config.ts"; import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts"; import type { TelegramModelMenuState } from "./menu-model.ts"; import type { MenuModel } from "./model.ts"; import type { TelegramVoiceReplyMode } from "./voice.ts"; export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup; export interface TelegramSettingsStateDeps { isProactivePushEnabled: () => boolean; areDraftPreviewsEnabled: () => boolean; getAssistantRenderingMode: () => TelegramAssistantRenderingMode; getTimeInjectionMode: () => TelegramTimeMode; getVoiceReplyMode: () => TelegramVoiceReplyMode; isVoiceReplyModeConfigured: () => boolean; } export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps { setProactivePushEnabled: (enabled: boolean) => Promise; setDraftPreviewsEnabled: (enabled: boolean) => Promise; setAssistantRenderingMode: ( mode: TelegramAssistantRenderingMode, ) => Promise; setVoiceReplyMode: ( mode: TelegramVoiceReplyMode | undefined, ) => Promise; setTimeInjectionMode: (mode: TelegramTimeMode) => Promise; } export interface TelegramSettingsMenuOpenDeps< TModel extends MenuModel = MenuModel, > extends TelegramSettingsStateDeps { getModelMenuState: () => Promise>; sendSettingsMenu: ( state: TelegramModelMenuState, text: string, replyMarkup: TelegramSettingsMenuReplyMarkup, ) => Promise; storeModelMenuState: (state: TelegramModelMenuState) => void; } export interface TelegramSettingsMenuCallbackDeps extends TelegramSettingsMutationDeps { updateSettingsMessage: ( text: string, replyMarkup: TelegramSettingsMenuReplyMarkup, ) => Promise; answerCallbackQuery: ( callbackQueryId: string, text?: string, ) => Promise; sectionRegistry?: TelegramSectionRegistry; } export interface TelegramSettingsMenuRuntime { openSettingsMenu: ( chatId: number, replyToMessageId: number, ctx: TContext, ) => Promise; handleCallbackQuery: ( query: { id: string; data?: string; message?: { message_id?: number; chat?: { id?: number } }; }, ctx: TContext, ) => Promise; updateSettingsMenuMessage: ( state: TelegramModelMenuState, ctx: TContext, ) => Promise; } export interface TelegramSettingsMenuMessageUpdateDeps extends TelegramSettingsStateDeps { updateSettingsMessage: ( text: string, replyMarkup: TelegramSettingsMenuReplyMarkup, ) => Promise; } export interface TelegramSettingsMenuRuntimeDeps< TContext, TModel extends MenuModel = MenuModel, > extends TelegramSettingsMutationDeps { getModelMenuState: ( chatId: number, ctx: TContext, ) => Promise>; getStoredModelMenuState: ( messageId: number | undefined, chatId?: number, ) => TelegramModelMenuState | undefined; storeModelMenuState: (state: TelegramModelMenuState) => void; editInteractiveMessage: ( chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramSettingsMenuReplyMarkup, ) => Promise; sendInteractiveMessage: ( chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramSettingsMenuReplyMarkup, ) => Promise; answerCallbackQuery: ( callbackQueryId: string, text?: string, ) => Promise; } export const SETTINGS_MENU_TITLE = "โš™๏ธ Settings:"; export const PROACTIVE_PUSH_SETTINGS_TITLE = "๐Ÿ“Œ Proactive push:"; export const DRAFT_PREVIEWS_SETTINGS_TITLE = "๐Ÿ“ Draft previews:"; export const ASSISTANT_RENDERING_SETTINGS_TITLE = "๐Ÿงพ Assistant rendering:"; export const TIME_INJECTION_MODE_SETTINGS_TITLE = "๐Ÿ•’ Time injection mode:"; export const VOICE_REPLY_MODE_SETTINGS_TITLE = "๐Ÿ‘„ Voice reply mode:"; type TelegramVoiceReplyModeSetting = TelegramVoiceReplyMode | "hidden"; function getVoiceReplyModeLabel(mode: TelegramVoiceReplyModeSetting): string { return mode; } function getTelegramSettingsStateValueLabel(value: string): string { return value.toLowerCase(); } function getVoiceReplyModeSetting( mode: TelegramVoiceReplyMode, configured: boolean, ): TelegramVoiceReplyModeSetting { return configured ? mode : "hidden"; } export function buildTelegramSettingsMenuText(): string { return SETTINGS_MENU_TITLE; } export function buildProactivePushSettingsText( proactivePushEnabled: boolean, ): string { return [ `${PROACTIVE_PUSH_SETTINGS_TITLE} ${proactivePushEnabled ? "on" : "off"}`, "", "Send successful local Pi task results to Telegram when the bridge is connected.", ].join("\n"); } export function buildDraftPreviewsSettingsText(enabled: boolean): string { return [ `${DRAFT_PREVIEWS_SETTINGS_TITLE} ${enabled ? "on" : "off"}`, "", "Show live answer drafts while the model is answering.", "", "- off (default): show native active status, then send one final answer.", "- on: stream safe Telegram Rich Draft frames before the final answer.", ].join("\n"); } export function buildAssistantRenderingSettingsText( mode: TelegramAssistantRenderingMode, ): string { return [ `${ASSISTANT_RENDERING_SETTINGS_TITLE} ${mode}`, "", "Choose how final assistant Markdown answers are delivered.", "", "- rich (default): use Telegram Native Rich Markdown.", "- html: use the legacy Markdown-to-HTML renderer.", ].join("\n"); } export function buildVoiceReplyModeSettingsText( mode: TelegramVoiceReplyMode, configured = true, ): string { return [ `${VOICE_REPLY_MODE_SETTINGS_TITLE} ${getVoiceReplyModeLabel( getVoiceReplyModeSetting(mode, configured), )}`, "", "Controls when pi-telegram converts assistant text replies into Telegram voice messages.", "", "- hidden (default): same behavior as 'manual', but no voice policy is added to prompt context.", "- manual: agent decides; explicit 'telegram_voice' markup still works and reply mode is visible in prompt context.", "- mirror: voice input prefers a voice reply; text input gracefully follows 'manual' behavior.", "- always: every reply is converted to voice when delivery succeeds.", ].join("\n"); } export function buildTimeInjectionModeSettingsText( mode: TelegramTimeMode, ): string { return [ `${TIME_INJECTION_MODE_SETTINGS_TITLE} ${mode}`, "", "Controls whether Telegram-originated prompts include a compact wall-clock [time] line.", "", "- hidden (default): no time line is added to prompt context.", "- always: add time to every Telegram turn.", "- interval: add time at most once per chat interval (default: 1 hour).", ].join("\n"); } export function buildTelegramSettingsMenuReplyMarkup( proactivePushEnabled: boolean, draftPreviewsEnabled: boolean, assistantRenderingModeOrVoiceReplyMode: TelegramAssistantRenderingMode | TelegramVoiceReplyMode, voiceReplyModeOrTimeInjectionMode: TelegramVoiceReplyMode | TelegramTimeMode, timeInjectionModeOrSectionRegistry?: TelegramTimeMode | TelegramSectionRegistry, sectionRegistryOrVoiceReplyModeConfigured?: TelegramSectionRegistry | boolean, voiceReplyModeConfigured = true, ): TelegramSettingsMenuReplyMarkup { const hasRenderingMode = assistantRenderingModeOrVoiceReplyMode === "rich" || assistantRenderingModeOrVoiceReplyMode === "html"; const assistantRenderingMode: TelegramAssistantRenderingMode = hasRenderingMode ? assistantRenderingModeOrVoiceReplyMode : "rich"; const voiceReplyMode = hasRenderingMode ? (voiceReplyModeOrTimeInjectionMode as TelegramVoiceReplyMode) : (assistantRenderingModeOrVoiceReplyMode as TelegramVoiceReplyMode); const timeInjectionMode = hasRenderingMode ? (timeInjectionModeOrSectionRegistry as TelegramTimeMode) : (voiceReplyModeOrTimeInjectionMode as TelegramTimeMode); const sectionRegistry = hasRenderingMode ? (sectionRegistryOrVoiceReplyModeConfigured as | TelegramSectionRegistry | undefined) : (timeInjectionModeOrSectionRegistry as TelegramSectionRegistry | undefined); const effectiveVoiceReplyModeConfigured = hasRenderingMode ? voiceReplyModeConfigured : typeof sectionRegistryOrVoiceReplyModeConfigured === "boolean" ? sectionRegistryOrVoiceReplyModeConfigured : true; const rows: Array> = [ [{ text: "โฌ†๏ธ Main menu", callback_data: "menu:back" }], ]; // Extension settings rows before built-in controls if (sectionRegistry) { const settingsRows = getTelegramExtensionSettingsRows(sectionRegistry); for (const row of settingsRows) { rows.push([{ text: row.label, callback_data: row.callback_data }]); } } rows.push( [ { text: `๐Ÿ‘„ Voice reply: ${getTelegramSettingsStateValueLabel( getVoiceReplyModeLabel( getVoiceReplyModeSetting(voiceReplyMode, effectiveVoiceReplyModeConfigured), ), )}`, callback_data: "settings:open:voice-reply", }, ], [ { text: `๐Ÿ•’ Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`, callback_data: "settings:open:time-injection", }, ], [ { text: `๐Ÿ“ Draft previews: ${draftPreviewsEnabled ? "on" : "off"}`, callback_data: "settings:open:draft-previews", }, ], [ { text: `๐Ÿงพ Rendering: ${assistantRenderingMode}`, callback_data: "settings:open:assistant-rendering", }, ], [ { text: `๐Ÿ“Œ Proactive push: ${proactivePushEnabled ? "on" : "off"}`, callback_data: "settings:open:proactive", }, ], ); return { inline_keyboard: rows }; } export async function openTelegramSettingsMenu< TModel extends MenuModel = MenuModel, >( deps: TelegramSettingsMenuOpenDeps, sectionRegistry?: TelegramSectionRegistry, ): Promise { const state = await deps.getModelMenuState(); const messageId = await deps.sendSettingsMenu( state, buildTelegramSettingsMenuText(), buildTelegramSettingsMenuReplyMarkup( deps.isProactivePushEnabled(), deps.areDraftPreviewsEnabled(), deps.getAssistantRenderingMode(), deps.getVoiceReplyMode(), deps.getTimeInjectionMode(), sectionRegistry, deps.isVoiceReplyModeConfigured(), ), ); if (messageId === undefined) return; state.messageId = messageId; state.mode = "settings"; deps.storeModelMenuState(state); } export function buildProactivePushSettingsReplyMarkup( proactivePushEnabled: boolean, ): TelegramSettingsMenuReplyMarkup { return { inline_keyboard: [ [{ text: "โฌ†๏ธ Back", callback_data: "settings:list" }], [ { text: proactivePushEnabled ? "๐ŸŸข On" : "โšซ๏ธ On", callback_data: "settings:set:proactive:on", }, { text: proactivePushEnabled ? "โšซ๏ธ Off" : "๐ŸŸก Off", callback_data: "settings:set:proactive:off", }, ], ], }; } export function buildDraftPreviewsSettingsReplyMarkup( enabled: boolean, ): TelegramSettingsMenuReplyMarkup { return { inline_keyboard: [ [{ text: "โฌ†๏ธ Back", callback_data: "settings:list" }], [ { text: enabled ? "๐ŸŸข On" : "โšซ๏ธ On", callback_data: "settings:set:draft-previews:on", }, { text: enabled ? "โšซ๏ธ Off" : "๐ŸŸก Off", callback_data: "settings:set:draft-previews:off" }, ], ], }; } export function buildAssistantRenderingSettingsReplyMarkup( mode: TelegramAssistantRenderingMode, ): TelegramSettingsMenuReplyMarkup { const modes: TelegramAssistantRenderingMode[] = ["rich", "html"]; return { inline_keyboard: [ [{ text: "โฌ†๏ธ Back", callback_data: "settings:list" }], ...modes.map((value) => [ { text: `${value === mode ? "๐ŸŸข " : ""}${value}`, callback_data: `settings:set:assistant-rendering:${value}`, }, ]), ], }; } export function buildTimeInjectionModeSettingsReplyMarkup( mode: TelegramTimeMode, ): TelegramSettingsMenuReplyMarkup { const modes: TelegramTimeMode[] = ["hidden", "always", "interval"]; return { inline_keyboard: [ [{ text: "โฌ†๏ธ Back", callback_data: "settings:list" }], ...modes.map((value) => [ { text: `${value === mode ? "๐ŸŸข " : ""}${value}`, callback_data: `settings:set:time-injection:${value}`, }, ]), ], }; } export function buildVoiceReplyModeSettingsReplyMarkup( mode: TelegramVoiceReplyMode, configured = true, ): TelegramSettingsMenuReplyMarkup { const activeMode = getVoiceReplyModeSetting(mode, configured); const modes: TelegramVoiceReplyModeSetting[] = [ "hidden", "manual", "mirror", "always", ]; return { inline_keyboard: [ [{ text: "โฌ†๏ธ Back", callback_data: "settings:list" }], ...modes.map((value) => [ { text: `${value === activeMode ? "๐ŸŸข " : ""}${getVoiceReplyModeLabel(value)}`, callback_data: `settings:set:voice-reply:${value}`, }, ]), ], }; } export async function updateTelegramSettingsMenuMessage( deps: TelegramSettingsMenuMessageUpdateDeps, sectionRegistry?: TelegramSectionRegistry, ): Promise { await deps.updateSettingsMessage( buildTelegramSettingsMenuText(), buildTelegramSettingsMenuReplyMarkup( deps.isProactivePushEnabled(), deps.areDraftPreviewsEnabled(), deps.getAssistantRenderingMode(), deps.getVoiceReplyMode(), deps.getTimeInjectionMode(), sectionRegistry, deps.isVoiceReplyModeConfigured(), ), ); } export async function updateProactivePushSettingsMessage( deps: TelegramSettingsMenuCallbackDeps, ): Promise { const proactivePushEnabled = deps.isProactivePushEnabled(); await deps.updateSettingsMessage( buildProactivePushSettingsText(proactivePushEnabled), buildProactivePushSettingsReplyMarkup(proactivePushEnabled), ); } export async function updateDraftPreviewsSettingsMessage( deps: TelegramSettingsMenuCallbackDeps, ): Promise { const enabled = deps.areDraftPreviewsEnabled(); await deps.updateSettingsMessage( buildDraftPreviewsSettingsText(enabled), buildDraftPreviewsSettingsReplyMarkup(enabled), ); } export async function updateAssistantRenderingSettingsMessage( deps: TelegramSettingsMenuCallbackDeps, ): Promise { const mode = deps.getAssistantRenderingMode(); await deps.updateSettingsMessage( buildAssistantRenderingSettingsText(mode), buildAssistantRenderingSettingsReplyMarkup(mode), ); } export async function updateTimeInjectionModeSettingsMessage( deps: TelegramSettingsMenuCallbackDeps, ): Promise { const mode = deps.getTimeInjectionMode(); await deps.updateSettingsMessage( buildTimeInjectionModeSettingsText(mode), buildTimeInjectionModeSettingsReplyMarkup(mode), ); } export async function updateVoiceReplyModeSettingsMessage( deps: TelegramSettingsMenuCallbackDeps, ): Promise { const mode = deps.getVoiceReplyMode(); const configured = deps.isVoiceReplyModeConfigured(); await deps.updateSettingsMessage( buildVoiceReplyModeSettingsText(mode, configured), buildVoiceReplyModeSettingsReplyMarkup(mode, configured), ); } type TelegramSettingsPageUpdater = ( deps: TelegramSettingsMenuCallbackDeps, ) => Promise; const TELEGRAM_SETTINGS_PAGE_UPDATERS = new Map< string, TelegramSettingsPageUpdater >([ [ "settings:list", (deps) => updateTelegramSettingsMenuMessage(deps, deps.sectionRegistry), ], ["settings:open:proactive", updateProactivePushSettingsMessage], ["settings:open:draft-previews", updateDraftPreviewsSettingsMessage], ["settings:open:rich-drafts", updateDraftPreviewsSettingsMessage], ["settings:open:assistant-rendering", updateAssistantRenderingSettingsMessage], ["settings:open:voice-reply", updateVoiceReplyModeSettingsMessage], ["settings:open:time-injection", updateTimeInjectionModeSettingsMessage], ["settings:open:time", updateTimeInjectionModeSettingsMessage], ]); /** Applies one setting value; resolves the toast text, or undefined when the value is invalid */ type TelegramSettingsValueApplier = ( value: string, deps: TelegramSettingsMenuCallbackDeps, ) => Promise; async function applyVoiceReplyModeSetting( mode: string, deps: TelegramSettingsMenuCallbackDeps, ): Promise { if ( mode !== "hidden" && mode !== "manual" && mode !== "mirror" && mode !== "always" ) { return undefined; } await deps.setVoiceReplyMode(mode === "hidden" ? undefined : mode); await updateVoiceReplyModeSettingsMessage(deps); return `Voice reply mode: ${mode}`; } async function applyTimeInjectionModeSetting( mode: string, deps: TelegramSettingsMenuCallbackDeps, ): Promise { const normalizedMode = mode === "off" ? "hidden" : mode; if ( normalizedMode !== "hidden" && normalizedMode !== "always" && normalizedMode !== "interval" ) { return undefined; } await deps.setTimeInjectionMode(normalizedMode); await updateTimeInjectionModeSettingsMessage(deps); return `Time injection: ${normalizedMode}`; } async function applyDraftPreviewsSetting( value: string, deps: TelegramSettingsMenuCallbackDeps, ): Promise { if (value !== "on" && value !== "off") return undefined; const enabled = value === "on"; await deps.setDraftPreviewsEnabled(enabled); await updateDraftPreviewsSettingsMessage(deps); return `Draft previews ${enabled ? "enabled" : "disabled"}`; } async function applyAssistantRenderingSetting( mode: string, deps: TelegramSettingsMenuCallbackDeps, ): Promise { if (mode !== "rich" && mode !== "html") return undefined; await deps.setAssistantRenderingMode(mode); await updateAssistantRenderingSettingsMessage(deps); return `Rendering: ${mode}`; } async function applyProactivePushSetting( value: string, deps: TelegramSettingsMenuCallbackDeps, ): Promise { if (value !== "on" && value !== "off") return undefined; const enabled = value === "on"; await deps.setProactivePushEnabled(enabled); await updateProactivePushSettingsMessage(deps); return `Proactive push ${enabled ? "enabled" : "disabled"}`; } const TELEGRAM_SETTINGS_VALUE_APPLIERS: Array< [prefix: string, apply: TelegramSettingsValueApplier] > = [ ["settings:set:voice-reply:", applyVoiceReplyModeSetting], ["settings:set:time-injection:", applyTimeInjectionModeSetting], ["settings:set:time:", applyTimeInjectionModeSetting], ["settings:set:draft-previews:", applyDraftPreviewsSetting], ["settings:set:rich-drafts:", applyDraftPreviewsSetting], ["settings:set:assistant-rendering:", applyAssistantRenderingSetting], ["settings:set:proactive:", applyProactivePushSetting], ]; export async function handleTelegramSettingsMenuCallbackAction( callbackQueryId: string, data: string | undefined, deps: TelegramSettingsMenuCallbackDeps, ): Promise { if (!data?.startsWith("settings:")) return false; const updatePage = TELEGRAM_SETTINGS_PAGE_UPDATERS.get(data); if (updatePage) { await updatePage(deps); await deps.answerCallbackQuery(callbackQueryId); return true; } const applier = TELEGRAM_SETTINGS_VALUE_APPLIERS.find(([prefix]) => data.startsWith(prefix), ); const toast = applier ? await applier[1](data.slice(applier[0].length), deps) : undefined; await deps.answerCallbackQuery(callbackQueryId, toast); return true; } export function createTelegramSettingsMenuRuntime< TContext, TModel extends MenuModel = MenuModel, >( deps: TelegramSettingsMenuRuntimeDeps, sectionRegistry?: TelegramSectionRegistry, ): TelegramSettingsMenuRuntime { return { openSettingsMenu: (chatId, _replyToMessageId, ctx) => openTelegramSettingsMenu( { getModelMenuState: () => deps.getModelMenuState(chatId, ctx), isProactivePushEnabled: deps.isProactivePushEnabled, areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled, getAssistantRenderingMode: deps.getAssistantRenderingMode, getVoiceReplyMode: deps.getVoiceReplyMode, isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured, getTimeInjectionMode: deps.getTimeInjectionMode, sendSettingsMenu: (state, text, replyMarkup) => deps.sendInteractiveMessage( state.chatId, text, "html", replyMarkup, ), storeModelMenuState: deps.storeModelMenuState, }, sectionRegistry, ), updateSettingsMenuMessage: (state) => updateTelegramSettingsMenuMessage( { isProactivePushEnabled: deps.isProactivePushEnabled, areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled, getAssistantRenderingMode: deps.getAssistantRenderingMode, getVoiceReplyMode: deps.getVoiceReplyMode, isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured, getTimeInjectionMode: deps.getTimeInjectionMode, updateSettingsMessage: (text, replyMarkup) => deps.editInteractiveMessage( state.chatId, state.messageId, text, "html", replyMarkup, ), }, sectionRegistry, ), handleCallbackQuery: async (query) => { if (!query.data?.startsWith("settings:")) return false; const state = deps.getStoredModelMenuState( query.message?.message_id, query.message?.chat?.id, ); if (!state) { const voiceMode = query.data.slice("settings:set:voice-reply:".length); if ( query.data.startsWith("settings:set:voice-reply:") && (voiceMode === "hidden" || voiceMode === "manual" || voiceMode === "mirror" || voiceMode === "always") ) { await deps.setVoiceReplyMode( voiceMode === "hidden" ? undefined : voiceMode, ); await deps.answerCallbackQuery( query.id, `Voice reply mode: ${voiceMode}`, ); return true; } if ( query.data === "settings:set:draft-previews:on" || query.data === "settings:set:draft-previews:off" || query.data === "settings:set:rich-drafts:on" || query.data === "settings:set:rich-drafts:off" ) { const enabled = query.data.endsWith(":on"); await deps.setDraftPreviewsEnabled(enabled); await deps.answerCallbackQuery( query.id, `Draft previews ${enabled ? "enabled" : "disabled"}`, ); return true; } if (query.data.startsWith("settings:set:assistant-rendering:")) { const mode = query.data.slice("settings:set:assistant-rendering:".length); if (mode === "rich" || mode === "html") { await deps.setAssistantRenderingMode(mode); await deps.answerCallbackQuery(query.id, `Rendering: ${mode}`); return true; } } const hasTimeInjectionPrefix = query.data.startsWith( "settings:set:time-injection:", ); const timeMode = hasTimeInjectionPrefix ? query.data.slice("settings:set:time-injection:".length) : query.data.slice("settings:set:time:".length); if ( (hasTimeInjectionPrefix || query.data.startsWith("settings:set:time:")) && (timeMode === "off" || timeMode === "hidden" || timeMode === "always" || timeMode === "interval") ) { const normalizedMode = timeMode === "off" ? "hidden" : timeMode; await deps.setTimeInjectionMode(normalizedMode); await deps.answerCallbackQuery( query.id, `Time injection: ${normalizedMode}`, ); return true; } await deps.answerCallbackQuery( query.id, "Interactive message expired.", ); return true; } return handleTelegramSettingsMenuCallbackAction(query.id, query.data, { isProactivePushEnabled: deps.isProactivePushEnabled, areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled, getAssistantRenderingMode: deps.getAssistantRenderingMode, getVoiceReplyMode: deps.getVoiceReplyMode, isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured, getTimeInjectionMode: deps.getTimeInjectionMode, setProactivePushEnabled: deps.setProactivePushEnabled, setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled, setAssistantRenderingMode: deps.setAssistantRenderingMode, setVoiceReplyMode: deps.setVoiceReplyMode, setTimeInjectionMode: deps.setTimeInjectionMode, updateSettingsMessage: (text, replyMarkup) => deps.editInteractiveMessage( state.chatId, state.messageId, text, "html", replyMarkup, ), answerCallbackQuery: deps.answerCallbackQuery, sectionRegistry, }); }, }; }