Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/app/stores/settings-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ export function setCompactOutputMode(enabled: boolean): void {
void writeSettingsFile(currentSettings);
}

export function getDeleteCompactProgressOnFinish(): boolean {
return currentSettings.deleteCompactProgressOnFinish ?? false;
}

export function setDeleteCompactProgressOnFinish(enabled: boolean): void {
currentSettings.deleteCompactProgressOnFinish = enabled;
void writeSettingsFile(currentSettings);
}

export function getShowThinkingContent(): boolean {
return currentSettings.showThinkingContent ?? true;
}
Expand Down Expand Up @@ -328,6 +337,7 @@ function applyInitialSettingsPreset(preset: Record<string, unknown>): void {
const knownKeys = new Set([
"ttsMode",
"compactOutputMode",
"deleteCompactProgressOnFinish",
"showThinkingContent",
"showAssistantRunFooter",
"responseStreamingMode",
Expand Down Expand Up @@ -363,7 +373,7 @@ function applyInitialSettingsPreset(preset: Record<string, unknown>): void {
currentSettings.responseStreamingMode = value as ResponseStreamingMode;
}
} else {
// Boolean settings: compactOutputMode, showThinkingContent, showAssistantRunFooter, sendDiffFileAttachments, promptQueueEnabled
// Boolean settings: compactOutputMode, deleteCompactProgressOnFinish, showThinkingContent, showAssistantRunFooter, sendDiffFileAttachments, promptQueueEnabled
if (typeof value !== "boolean") {
throw new Error(
`INITIAL_SETTINGS_PRESET: "${key}" must be a boolean.`,
Expand All @@ -374,6 +384,10 @@ function applyInitialSettingsPreset(preset: Record<string, unknown>): void {
if (currentSettings.compactOutputMode === undefined)
currentSettings.compactOutputMode = value;
break;
case "deleteCompactProgressOnFinish":
if (currentSettings.deleteCompactProgressOnFinish === undefined)
currentSettings.deleteCompactProgressOnFinish = value;
break;
case "showThinkingContent":
if (currentSettings.showThinkingContent === undefined)
currentSettings.showThinkingContent = value;
Expand Down
1 change: 1 addition & 0 deletions src/app/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Settings {
pinnedMessageId?: number | undefined;
ttsMode?: "off" | "all" | "auto" | undefined;
compactOutputMode?: boolean | undefined;
deleteCompactProgressOnFinish?: boolean | undefined;
showThinkingContent?: boolean | undefined;
showAssistantRunFooter?: boolean | undefined;
responseStreamingMode?: ResponseStreamingMode | undefined;
Expand Down
13 changes: 13 additions & 0 deletions src/bot/callbacks/settings-callback-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import type { Context } from "grammy";
import { isTtsConfigured } from "../../app/services/tts-service.js";
import {
getCompactOutputMode,
getDeleteCompactProgressOnFinish,
getPromptQueueEnabled,
getResponseStreamingMode,
getSendDiffFileAttachments,
getShowAssistantRunFooter,
getShowThinkingContent,
getTtsMode,
setCompactOutputMode,
setDeleteCompactProgressOnFinish,
setPromptQueueEnabled,
setResponseStreamingMode,
setSendDiffFileAttachments,
Expand All @@ -26,6 +28,7 @@ import {
SETTINGS_ASSISTANT_FOOTER_CALLBACK,
SETTINGS_CALLBACK_PREFIX,
SETTINGS_COMPACT_OUTPUT_CALLBACK,
SETTINGS_DELETE_PROGRESS_ON_FINISH_CALLBACK,
SETTINGS_DIFF_FILES_CALLBACK,
SETTINGS_PROMPT_QUEUE_CALLBACK,
SETTINGS_RESPONSE_STREAMING_CALLBACK,
Expand Down Expand Up @@ -84,6 +87,16 @@ export async function handleSettingsCallback(ctx: Context): Promise<boolean> {
return true;
}

if (callbackData === SETTINGS_DELETE_PROGRESS_ON_FINISH_CALLBACK) {
setDeleteCompactProgressOnFinish(!getDeleteCompactProgressOnFinish());
const { text, keyboard } = buildSettingsMenuView();
await ctx.answerCallbackQuery({ text: t("settings.saved") });
await ctx.editMessageText(text, {
reply_markup: appendInlineMenuCancelButton(keyboard, "settings"),
});
return true;
}

if (callbackData === SETTINGS_THINKING_CONTENT_CALLBACK) {
setShowThinkingContent(!getShowThinkingContent());
const { text, keyboard } = buildSettingsMenuView();
Expand Down
10 changes: 9 additions & 1 deletion src/bot/menus/settings-menu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InlineKeyboard } from "grammy";
import {
getCompactOutputMode,
getDeleteCompactProgressOnFinish,
getPromptQueueEnabled,
getResponseStreamingMode,
getSendDiffFileAttachments,
Expand All @@ -14,6 +15,7 @@ import { t } from "../../i18n/index.js";

export const SETTINGS_CALLBACK_PREFIX = "settings:";
export const SETTINGS_COMPACT_OUTPUT_CALLBACK = `${SETTINGS_CALLBACK_PREFIX}compact_output`;
export const SETTINGS_DELETE_PROGRESS_ON_FINISH_CALLBACK = `${SETTINGS_CALLBACK_PREFIX}delete_progress_on_finish`;
export const SETTINGS_THINKING_CONTENT_CALLBACK = `${SETTINGS_CALLBACK_PREFIX}thinking_content`;
export const SETTINGS_RESPONSE_STREAMING_CALLBACK = `${SETTINGS_CALLBACK_PREFIX}response_streaming`;
export const SETTINGS_DIFF_FILES_CALLBACK = `${SETTINGS_CALLBACK_PREFIX}diff_files`;
Expand Down Expand Up @@ -45,6 +47,7 @@ export function formatResponseStreamingModeValue(mode: ResponseStreamingMode): s

export function buildSettingsMenuView(): { text: string; keyboard: InlineKeyboard } {
const compactOutputMode = getCompactOutputMode();
const deleteCompactProgressOnFinish = getDeleteCompactProgressOnFinish();
const showThinkingContent = getShowThinkingContent();
const responseStreamingMode = getResponseStreamingMode();
const sendDiffFileAttachments = getSendDiffFileAttachments();
Expand All @@ -57,7 +60,12 @@ export function buildSettingsMenuView(): { text: string; keyboard: InlineKeyboar
SETTINGS_COMPACT_OUTPUT_CALLBACK,
);

if (!compactOutputMode) {
if (compactOutputMode) {
keyboard.row().text(
`${t("settings.delete_progress_on_finish.label")}: ${formatBooleanSettingValue(deleteCompactProgressOnFinish)}`,
SETTINGS_DELETE_PROGRESS_ON_FINISH_CALLBACK,
);
} else {
keyboard.row().text(
`${t("settings.thinking_content.label")}: ${formatBooleanSettingValue(showThinkingContent)}`,
SETTINGS_THINKING_CONTENT_CALLBACK,
Expand Down
28 changes: 27 additions & 1 deletion src/bot/services/event-subscription-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { ToolMessageBatcher } from "../../app/formatters/tool-message-batcher.js";
import {
getCompactOutputMode,
getDeleteCompactProgressOnFinish,
getResponseStreamingMode,
getSendDiffFileAttachments,
getShowAssistantRunFooter,
Expand Down Expand Up @@ -262,6 +263,29 @@ class EventSubscriptionService implements BotEventSubscriptionService {
throw error;
}
},
deleteText: async (sessionId, messageId) => {
if (!this.botInstance || !this.chatIdInstance || this.chatIdInstance <= 0) {
throw new Error("Bot context missing for compact progress delete");
}

const currentSession = getCurrentSession();
if (!currentSession || currentSession.id !== sessionId) {
throw new Error(`Compact progress session mismatch for delete: ${sessionId}`);
}

await this.botInstance.api.deleteMessage(this.chatIdInstance, messageId).catch((error) => {
const errorMessage =
error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
if (
errorMessage.includes("message to delete not found") ||
errorMessage.includes("message identifier is not specified")
) {
return;
}

throw error;
});
},
});

this.toolCallStreamer = new ToolCallStreamer({
Expand Down Expand Up @@ -1637,7 +1661,9 @@ class EventSubscriptionService implements BotEventSubscriptionService {
return existingTask;
}

const nextTask = this.compactProgressStreamer.finalize(sessionId).finally(() => {
const nextTask = this.compactProgressStreamer
.finalize(sessionId, getDeleteCompactProgressOnFinish())
.finally(() => {
if (this.compactProgressFinalizationTasks.get(sessionId) === nextTask) {
this.compactProgressFinalizationTasks.delete(sessionId);
}
Expand Down
34 changes: 30 additions & 4 deletions src/bot/streaming/compact-progress-streamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface CompactProgressStreamerOptions {
throttleMs: StreamThrottleMs;
sendText: (sessionId: string, text: string) => Promise<number>;
editText: (sessionId: string, messageId: number, text: string) => Promise<void>;
deleteText?: (sessionId: string, messageId: number) => Promise<void>;
}

function getErrorMessage(error: unknown): string {
Expand All @@ -44,11 +45,13 @@ export class CompactProgressStreamer {
private readonly throttleMs: StreamThrottleMs;
private readonly sendText: CompactProgressStreamerOptions["sendText"];
private readonly editText: CompactProgressStreamerOptions["editText"];
private readonly deleteText: CompactProgressStreamerOptions["deleteText"];

constructor({ throttleMs, sendText, editText }: CompactProgressStreamerOptions) {
constructor({ throttleMs, sendText, editText, deleteText }: CompactProgressStreamerOptions) {
this.throttleMs = throttleMs;
this.sendText = sendText;
this.editText = editText;
this.deleteText = deleteText;
}

private resolveThrottleMs(sessionId: string): number {
Expand Down Expand Up @@ -92,25 +95,48 @@ export class CompactProgressStreamer {
this.states.get(sessionId)?.filePaths.add(normalizedPath);
}

async finalize(sessionId: string): Promise<void> {
async finalize(sessionId: string, deleteOnFinish = false): Promise<void> {
const state = this.states.get(sessionId);
if (!state) {
return;
}

this.clearTimer(state);
await state.task.catch(() => false);

if (deleteOnFinish && this.deleteText) {
await this.deleteProgressMessage(state);
this.cancelState(state);
this.states.delete(sessionId);
return;
}

state.latestText = t("progress.compact.done", {
header: t("progress.compact.finished_header"),
tools: state.toolCallIds.size,
files: state.filePaths.size,
});

this.clearTimer(state);
await state.task.catch(() => false);
await this.syncState(state, "finalize");
this.cancelState(state);
this.states.delete(sessionId);
}

private async deleteProgressMessage(state: CompactProgressState): Promise<void> {
if (state.messageId === null) {
return;
}

try {
await this.deleteText?.(state.sessionId, state.messageId);
} catch (error) {
logger.error(
`[CompactProgress] Failed to delete progress message: session=${state.sessionId}, error=${getErrorMessage(error)}`,
error,
);
}
}

clearSession(sessionId: string, reason: string): void {
const state = this.states.get(sessionId);
if (!state) {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const ar: I18nDictionary = {

"settings.menu.title": "⚙️ إعدادات البوت\nاضغط على إعداد لتبديل قيمته:",
"settings.compact_output.label": "وضع الإخراج المختصر",
"settings.delete_progress_on_finish.label": "حذف التقدم عند الانتهاء",
"settings.thinking_content.label": "محتوى التفكير",
"settings.response_streaming.label": "بث الرد",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const de: I18nDictionary = {
"settings.menu.title":
"⚙️ Bot-Einstellungen\nTippen Sie auf eine Einstellung, um ihren Wert umzuschalten:",
"settings.compact_output.label": "Kompakte Ausgabe",
"settings.delete_progress_on_finish.label": "Fortschritt beim Abschluss löschen",
"settings.thinking_content.label": "Thinking-Inhalt",
"settings.response_streaming.label": "Antwort-Streaming",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const en = {

"settings.menu.title": "⚙️ Bot settings\nTap a setting to toggle its value:",
"settings.compact_output.label": "Compact output mode",
"settings.delete_progress_on_finish.label": "Delete progress on finish",
"settings.thinking_content.label": "Thinking content",
"settings.response_streaming.label": "Response streaming",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const es: I18nDictionary = {

"settings.menu.title": "⚙️ Ajustes del bot\nToca un ajuste para cambiar su valor:",
"settings.compact_output.label": "Salida compacta",
"settings.delete_progress_on_finish.label": "Borrar progreso al terminar",
"settings.thinking_content.label": "Contenido de thinking",
"settings.response_streaming.label": "Streaming de respuesta",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export const fr: I18nDictionary = {

"settings.menu.title": "⚙️ Paramètres du bot\nTouchez un paramètre pour basculer sa valeur :",
"settings.compact_output.label": "Sortie compacte",
"settings.delete_progress_on_finish.label": "Supprimer la progression à la fin",
"settings.thinking_content.label": "Contenu thinking",
"settings.response_streaming.label": "Streaming de réponse",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const it: I18nDictionary = {

"settings.menu.title": "⚙️ Impostazioni del bot\nPremi su un'impostazione per cambiarne il valore:",
"settings.compact_output.label": "Modalità output compatta",
"settings.delete_progress_on_finish.label": "Elimina progresso al termine",
"settings.thinking_content.label": "Contenuto del pensiero",
"settings.response_streaming.label": "Streaming della risposta",
"settings.response_streaming.edit": "modifica",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const ko: I18nDictionary = {

"settings.menu.title": "⚙️ 봇 설정\n항목을 탭하여 값을 전환하세요:",
"settings.compact_output.label": "간결 출력 모드",
"settings.delete_progress_on_finish.label": "완료 시 진행 삭제",
"settings.thinking_content.label": "생각 내용",
"settings.response_streaming.label": "응답 스트리밍",
"settings.response_streaming.edit": "편집",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const pt: I18nDictionary = {
"settings.menu.title":
"⚙️ Configurações do bot\nToque em uma configuração para alternar seu valor:",
"settings.compact_output.label": "Modo de saída compacta",
"settings.delete_progress_on_finish.label": "Apagar progresso ao terminar",
"settings.thinking_content.label": "Conteúdo do thinking",
"settings.response_streaming.label": "Streaming de resposta",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const ru: I18nDictionary = {

"settings.menu.title": "⚙️ Настройки бота\nНажмите на параметр, чтобы переключить его значение:",
"settings.compact_output.label": "Компактный вывод",
"settings.delete_progress_on_finish.label": "Удалять прогресс по завершении",
"settings.thinking_content.label": "Содержимое thinking",
"settings.response_streaming.label": "Стриминг ответа",
"settings.response_streaming.edit": "edit",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const zh: I18nDictionary = {

"settings.menu.title": "⚙️ 机器人设置\n点按设置项以切换其值:",
"settings.compact_output.label": "紧凑输出模式",
"settings.delete_progress_on_finish.label": "完成后删除进度",
"settings.thinking_content.label": "思考内容",
"settings.response_streaming.label": "回复流式模式",
"settings.response_streaming.edit": "edit",
Expand Down
3 changes: 2 additions & 1 deletion tests/app/stores/settings-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ describe("app/stores/settings-store", () => {
vi.resetModules();
vi.stubEnv(
"INITIAL_SETTINGS_PRESET",
'{"showAssistantRunFooter":false,"compactOutputMode":true,"ttsMode":"auto","responseStreamingMode":"draft","sendDiffFileAttachments":false,"showThinkingContent":false,"promptQueueEnabled":true}',
'{"showAssistantRunFooter":false,"compactOutputMode":true,"deleteCompactProgressOnFinish":true,"ttsMode":"auto","responseStreamingMode":"draft","sendDiffFileAttachments":false,"showThinkingContent":false,"promptQueueEnabled":true}',
);

const store = await import("../../../src/app/stores/settings-store.js");
await store.loadSettings();

expect(store.getShowAssistantRunFooter()).toBe(false);
expect(store.getCompactOutputMode()).toBe(true);
expect(store.getDeleteCompactProgressOnFinish()).toBe(true);
expect(store.getTtsMode()).toBe("auto");
expect(store.getResponseStreamingMode()).toBe("draft");
expect(store.getSendDiffFileAttachments()).toBe(false);
Expand Down
Loading
Loading