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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## [4.0.27] - 2026-09-17

### Added

- 🐦 **Чубрики прилетели на поле ввода!** Теперь на панели с сообщениями живёт твой питомец — сидит на верхней границе поля и переезжает вместе с ним. Никаких слетающих координат при смене разрешения: место посадки хранится в долях поля :)
- два режима: **приклеен к полю** (по умолчанию) и **свободный** — можно мышкой перетащить куда угодно;
- размер меняется прямо на экране — потяни за синий уголок;
- посадить в нужную точку можно за секунду: жми **F8** и кликни туда, где должен сидеть чубрик.
- 🎨 **Выбор питомца из папки** — открываешь настройки → «Чубрики» → и видишь всех своих чубриков сеткой. Кликнул на любого — он сразу на поле.
- 📤 **Загрузить своего** — кнопка «Загрузить…» принимает любую картинку. Большие фото автоматом сжимаются до 600×600, чтобы не тормозить :3
- 🎁 **2 чубрика из коробки** — не надо ничего искать, при первом запуске уже есть с кем поиграться.
- ✂️ **Вырезать фон у GIF** — если у гифки белый (или любой другой) фон, жми кнопку «🎨 Фон», пипеткой тыкай в ненужный цвет и жми «Применить». Фон исчезнет **во всех кадрах**, анимация останется ^_^
- 🛠 **Debug-режим** — тумблер в настройках открывает горячие клавиши:
- **F8** — прицел (поставить чубрика в точку)
- **F9** — показать рамку поля ввода
- **F10** — переключить режим «приклеен / свободный»
- **F11** — сброс размера
- 🐦 **Чубрики в Telegram-боте** — теперь питомца можно менять прямо из телеги: /settings → 🐦 Чубрики. Выбрал — и он уже в приложении бегает!
- ⏱ **Свой таймаут для скриптов** — если AI запускает длинную задачу и она падает с «превышено 60 секунд», теперь можно поставить хоть 1000. Настройки → «Агент» → «Таймаут JS-скриптов».
- 📡 **Всё меняется на лету** — что бы ты ни менял в боте (пет, фон, размер, блюр панели), в приложении это сразу видно. Никаких перезапусков :D

### Fixed

- 🐛 **Чубрик не возвращался после выключения** — включаешь его обратно в настройках, а он невидимка. Починили — теперь появляется на своём месте.
- 🐛 **Текст в настройках рвался по буквам** — в узкой панели подписи типа «Выбрать пета» превращались в вертикальную мешанину. Теперь всё чинно.

## [4.0.26] - 2026-09-17

### Added
Expand Down
15 changes: 15 additions & 0 deletions botsrc/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const STRINGS = {
"settings.page.ui": "🎨 Интерфейс",
"settings.page.glass": "🪟 Стекло и панель",
"settings.page.agent": "🤖 Агент и приватность",
"settings.page.pets": "🐦 Чубрики",
"settings.page.tg": "📡 Telegram-бот",
"settings.page.lang": "🌐 Язык",

Expand All @@ -69,6 +70,12 @@ const STRINGS = {
"settings.glass.text": "Прозрачность, размытие и цвета панели Cookie Code.",
"settings.agent.title": "🤖 <b>Агент и приватность</b>",
"settings.agent.text": "Подтверждения, служебные сообщения, форматтеры.",
"settings.pets.title": "🐦 <b>Чубрики (петы)</b>",
"settings.pets.text": "Выбор спрайта и debug-режим. Файлы — в папке pets.",
"label.petEnabled": "Пет включён",
"label.petId": "Пет (спрайт)",
"label.petDebugMode": "Debug-режим пета",
"label.jsTimeoutSec": "Таймаут JS-скриптов",
"settings.tg.title": "📡 <b>Telegram-бот</b>",
"settings.tg.text": "Управление ботом и уведомлениями.",
"settings.lang.title": "🌐 <b>Язык бота</b>",
Expand Down Expand Up @@ -289,6 +296,7 @@ const STRINGS = {
"settings.page.ui": "🎨 Interface",
"settings.page.glass": "🪟 Glass &amp; panel",
"settings.page.agent": "🤖 Agent &amp; privacy",
"settings.page.pets": "🐦 Pets",
"settings.page.tg": "📡 Telegram bot",
"settings.page.lang": "🌐 Language",

Expand All @@ -299,6 +307,13 @@ const STRINGS = {
"Transparency, blur and colors of the Cookie Code panel.",
"settings.agent.title": "🤖 <b>Agent &amp; privacy</b>",
"settings.agent.text": "Approvals, system messages, formatters.",
"settings.pets.title": "🐦 <b>Pets</b>",
"settings.pets.text":
"Sprite selection and debug mode. Files live in the pets folder.",
"label.petEnabled": "Pet enabled",
"label.petId": "Pet (sprite)",
"label.petDebugMode": "Pet debug mode",
"label.jsTimeoutSec": "JS script timeout",
"settings.tg.title": "📡 <b>Telegram bot</b>",
"settings.tg.text": "Bot control and notifications.",
"settings.lang.title": "🌐 <b>Bot language</b>",
Expand Down
122 changes: 116 additions & 6 deletions botsrc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ const SETTINGS_PAGES = {
{ goto: "ui", labelKey: "settings.page.ui" },
{ goto: "glass", labelKey: "settings.page.glass" },
{ goto: "agent", labelKey: "settings.page.agent" },
{ goto: "pets", labelKey: "settings.page.pets" },
{ goto: "tg", labelKey: "settings.page.tg" },
{ goto: "lang", labelKey: "settings.page.lang" },
],
Expand Down Expand Up @@ -697,6 +698,44 @@ const SETTINGS_PAGES = {
labelKey: "label.dangerousPatterns",
type: "multiline",
},
{
key: "jsTimeoutSec",
labelKey: "label.jsTimeoutSec",
type: "number",
step: 10,
min: 10,
max: 1000,
unit: "s",
},
],
back: true,
},
pets: {
titleKey: "settings.pets.title",
textKey: "settings.pets.text",
items: [
{
key: "petEnabled",
labelKey: "label.petEnabled",
type: "bool",
},
{
key: "petId",
labelKey: "label.petId",
type: "enum",
dynamicValues: () => {
const pets = _listPets();
if (!pets.length) {
return [["", _t("common.empty")]];
}
return pets.map((p) => [p.id, p.id]);
},
},
{
key: "petDebugMode",
labelKey: "label.petDebugMode",
type: "bool",
},
],
back: true,
},
Expand Down Expand Up @@ -770,6 +809,32 @@ function _findSetting(key) {
return null;
}

/**
* Список спрайтов петов из <userData>/pets (или [] при ошибке).
* Возвращает [{id, label, file}].
*/
function _listPets() {
try {
const fs = require("fs");
const path = require("path");
const { app } = require("electron");
const dir = path.join(app.getPath("userData"), "pets");
if (!fs.existsSync(dir)) return [];
const EXTS = [".png", ".gif", ".webp", ".jpg", ".jpeg"];
return fs
.readdirSync(dir)
.filter((f) => EXTS.includes(path.extname(f).toLowerCase()))
.sort()
.map((f) => {
const ext = path.extname(f);
const base = f.slice(0, -ext.length);
return { id: base, label: base, file: path.join(dir, f) };
});
} catch (_) {
return [];
}
}

function _getVal(key) {
try {
return settingsStore.readSettings()[key];
Expand Down Expand Up @@ -798,10 +863,27 @@ function _label(item) {
return item.label || item.key || "";
}

/**
* Получить актуальный список значений для enum.
* Если у item задан dynamicValues (функция), — вызываем её и получаем свежий
* массив [value, label]. Иначе — статический item.values.
*/
function _enumValues(item) {
if (typeof item.dynamicValues === "function") {
try {
const v = item.dynamicValues();
if (Array.isArray(v) && v.length) return v;
} catch (err) {
_log("error", "dynamicValues error:", err.message);
}
}
return item.values || [];
}

function _formatValue(item, value) {
if (item.type === "bool") return value ? _t("common.on") : _t("common.off");
if (item.type === "enum") {
const found = (item.values || []).find(([v]) => v === value);
const found = _enumValues(item).find(([v]) => v === value);
return found ? found[1] : String(value);
}
if (item.type === "number") return String(value) + (item.unit || "");
Expand Down Expand Up @@ -846,7 +928,7 @@ function _renderPage(pageName) {
} else if (item.type === "enum") {
const cur = _formatValue(item, v);
keyboard.push([{ text: lbl + ": " + cur, callback_data: "st_noop" }]);
const opts = (item.values || []).map(([val, lblVal]) => ({
const opts = _enumValues(item).map(([val, lblVal]) => ({
text: lblVal,
callback_data: "st_s_" + item.key + "__" + val,
}));
Expand Down Expand Up @@ -978,6 +1060,7 @@ async function _handleSettingsCallback(data, cbq) {
try {
await applySettings();
} catch (_) {}
_notifySettingsChanged({ [key]: next });
}
await _showSettingsMenu(chatId, state.page, msgId);
return;
Expand All @@ -991,10 +1074,14 @@ async function _handleSettingsCallback(data, cbq) {
await telegramBot.answerCallbackQuery(cbq.id, {
text: ok ? _t("cb.saved") : _t("common.error"),
});
if (ok && key === "language") {
try {
await applySettings();
} catch (_) {}
if (ok) {
if (key === "language") {
try {
await applySettings();
} catch (_) {}
}
// Пуш в UI: enum мог изменить выбор пета, язык UI, режим approval и т.д.
_notifySettingsChanged({ [key]: val });
}
await _showSettingsMenu(chatId, state.page, msgId);
return;
Expand All @@ -1020,6 +1107,7 @@ async function _handleSettingsCallback(data, cbq) {
await telegramBot.answerCallbackQuery(cbq.id, {
text: ok ? String(val) + (item.unit || "") : _t("common.error"),
});
if (ok) _notifySettingsChanged({ [key]: val });
await _showSettingsMenu(chatId, state.page, msgId);
return;
}
Expand Down Expand Up @@ -1576,6 +1664,7 @@ async function _handleSettingsInput(chatId, text) {
await applySettings();
} catch (_) {}
}
if (ok) _notifySettingsChanged({ [key]: value });
await telegramBot.sendMessage(
ok
? _t("common.saved", { label: _esc(_label(item)) })
Expand Down Expand Up @@ -2074,6 +2163,27 @@ async function notifyToolResult(toolName, ok, detail) {
}

/** Применить настройки: пересоздать конфиг бота и (при необходимости) запустить polling. */
/**
* Разослать всем открытым окнам приложения событие "настройки изменились".
* Вызывается после каждого изменения settings.json через TG-бота, чтобы UI
* мгновенно подхватил изменения (перечитал настройки и применил эффекты).
*
* @param {object} [patch] объект с изменёнными ключами { key: value };
* если не задан — окна перечитают всё целиком.
*/
function _notifySettingsChanged(patch) {
try {
const wins = windowState.getAllWindows ? windowState.getAllWindows() : [];
for (const win of wins) {
try {
if (!win || win.isDestroyed()) continue;
if (!win.webContents || win.webContents.isDestroyed()) continue;
win.webContents.send("cuckoo-settings-changed", patch || null);
} catch (_) {}
}
} catch (_) {}
}

async function applySettings() {
const cfg = _read();
telegramBot.configure(cfg.token, cfg.chatId, cfg.allowedUserId);
Expand Down
Loading
Loading