dsh-tweaks
Набор твиков DeepSeek Harness: масштаб интерфейса, дальше — перевод и другое. Каждый твик включается отдельно.
- Stars
- 0
- Language
- JavaScript
- Created
- Oct 8, 2026
- Updated
- Oct 16, 2026
Introduction
dsh-tweaks
Твики DeepSeek Harness одним пакетом. Каждый включается своей галочкой на странице пакета (Settings → Plugins → dsh-tweaks) и выключается там же — без перезапуска.
| Твик | Что делает |
|---|---|
zoom | Масштаб всего интерфейса: пара клавиш меняет размер интерфейса, а не только шрифт переписки |
contextMenu | Своё меню по правой кнопке: системное меню Electron не масштабируется вместе с интерфейсом |
whisper | Распознавание русской речи локальной моделью Whisper (tweaks/whisper-host.js, Python-воркер): выключено по умолчанию |
trafficLight | Светофор дорогих часов DeepSeek: индикатор в шапке и под полем ввода, пауза расписаний и автоматической работы на пике |
Клавиши (твик zoom)
| Действие | Основной блок | Нумпад | Мышь |
|---|---|---|---|
| Крупнее | Ctrl + = | Ctrl + + | Ctrl + колесо вверх |
| Мельче | Ctrl + - | Ctrl + − | Ctrl + колесо вниз |
| Сброс 100% | Ctrl + 0 | Ctrl + 0 | — |
Shift не нужен: клавиша читается по позиции, поэтому Ctrl + Shift + = тоже
работает, и раскладка не важна. Шаг — 5%, диапазон — 50…200%. Размер показывается
плашкой в правом нижнем углу 1,2 с и хранится в localStorage (ключ dsh.ui-zoom.v1).
В браузере Ctrl + = / - / 0 забирает страница, поэтому там те же действия
доступны с Alt. Переназначить любую комбинацию: Settings → «Горячие клавиши».
Установка
plugin_manager action=install_bundle target=<путь к папке плагина>
Вручную: добавить "dsh-tweaks": "link:<путь>" в зависимости профиля, вписать
dsh-tweaks в dsh.profile.bundles, затем pnpm install в папке профиля.
Как устроено
client.js— браузерная половина: реестрTWEAKS, чтение флагов, форма, код твиков.index.js— хост-половина: схемаConfigс флагами твиков и подключение хост-частей (tweaks/whisper-host.js,tweaks/traffic-light-host.js).cordis.patch.yml— одна запись пакета (id: zoom); строка на странице Plugins выключает пакет целиком, поэтому отдельные твики живут флагами, а не записями.locale/en.json,locale/ru.json— подписи карточки пакета (meta.title,meta.description); блокmeta:вcordis.patch.ymlстраница Plugins не читает.
Включение и выключение твиков
Флаг твика — поле схемы Config в index.js, помеченное .volatile(): только такие
поля DSH показывает в настройках и принимает на запись, а их смена доходит до живой
записи без перезапуска. Имя поля = id твика в реестре TWEAKS в client.js.
Форму DSH не рисует — её рисует плагин. Галочки живут в слоте
plugins.bundle.config с ключом dsh-tweaks, то есть на странице самого пакета.
Клик по галочке сразу пишет значение (set с прочитанной ревизией) в клиентский
сервис configForms — тот же, откуда плагин читает флаги: namespace формы — id
записи в cordis.patch.yml, то есть zoom.
client.js держит один набор твиков на пакет: apply(ctx) читает флаги,
включает включённые, выключает выключенные и подписывается на изменения. Ресурсы
твика регистрируются через api.own и снимаются при выключении, поэтому повторное
включение не оставляет двойных обработчиков. Пока конфигурация не готова
(configForms нет или status !== 'ready'), работают дефолты реестра: zoom,
contextMenu и trafficLight включены, whisper — нет. В готовом снимке
отсутствие поля — это тоже дефолт реестра: пропавшее поле не включает твик,
выключенный по умолчанию.
Выключение zoom возвращает интерфейс к 100% (значение в localStorage остаётся,
включение вернёт прежний масштаб); попапы при этом возвращаются к исходным
координатам. Выключение contextMenu возвращает системное меню.
Новый твик: поле в Config (index.js), запись в TWEAKS (client.js) и, если у
твика есть код, свой регион в client.js — /* #region tweak-<id> */. Соседей это
не задевает.
Хост-часть твика
Хост-часть твика (если нужна) — отдельный файл tweaks/<id>-host.js, который
index.js подключает по флагу. Клиентская половина у пакета одна, отдельного
модуля на твик не бывает; клиентской части может не быть вовсе — у whisper её
нет, галочка включает хост-часть.
Плагин читает флаги через событие loader/volatile-update: смена одних только
volatile-полей не перезапускает плагин, а DSH компилирует новые значения в уже
выданную конфигурацию и уведомляет владельца записи. Значения volatile-полей
приходят ссылками Volatile (.get()), поэтому хост-часть получает развёрнутую
копию конфигурации обычными значениями. Жизненный цикл твика whisper: флаг
включён — activate(ctx, config); выключен — deactivate(); сменился
whisperModel у включённого — deactivate() и следом activate() с новыми
значениями (размер модели провайдер читает один раз при регистрации).
Переключения идут очередью, а выгрузка плагина ждёт deactivate() — Python-воркер
не переживает твик.
Жизненный цикл твика trafficLight проще: activate(ctx, config) ставит
слушателей и таймер, а смена значений политики приходит в update(config) без
перезапуска — удержанные на красном напоминания переживают правку окон.
deactivate() снимает слушателей, будит ожидающие вызовы и возвращает удержанное
в работу.
Модуль твика импортируется динамически и в try/catch: файла может не быть
(другая ветка, неполная поставка), и тогда причина уходит в лог, а схема Config
и остальные твики продолжают работать. Поле files в package.json включает
tweaks/**, поэтому Python-воркер (tweaks/worker.py) попадает в поставку.
При первом включении твика «речь» хост-половина один раз выставляет настройки
распознавания: после успешного activate (провайдер whisper-local уже
зарегистрирован) идёт ctx.speechToText.configure({ providerId: 'whisper-local', language: 'ru' }), а затем в своё служебное volatile-поле whisperSelectionApplied
записывается признак «выставлено». Галочки у этого поля нет: форма строится из
реестра TWEAKS, а не из схемы. Дальше выбор не перезаписывается — сменил
пользователь провайдера или язык руками, configure больше не зовётся. Ошибка
(службы распознавания нет, язык не поддержан, служба настроек недоступна) уходит
в лог и твик не роняет; признак при этом не ставится, поэтому при следующем
включении попытка повторяется.
Светофор дорогих часов
Правило тарифов DeepSeek (сверено с официальной страницей цен, ред. 2026-09-10): пиковая цена действует по будням 09:00–12:00 и 14:00–18:00 по Пекину (для Москвы это 04:00–07:00 и 09:00–13:00), кроме официальных праздников КНР; всё остальное время — полцены. Полуинтервалы: граница 12:00 уже вне пика, 14:00 — снова пик.
Индикатор — круглая лампа в левом верхнем углу (рядом с логотипом) и её дубль
под полем ввода, самым первым в строке дока (перед системными показателями): зелёный — полцены, жёлтый мигает
за выбранное время до пика, красный — пик. На красном при работающих сессиях вокруг
лампы расходятся волны. Панель открывается только по нажатию на лампу и повторяет
системный попап (беседа «Token usage»/контекст): матовый полупрозрачный фон
--dsw-specific-menu, блюр подложки --dsw-menu-backdrop-filter, системные тень и
скругление. Внутри — горизонтальная шкала суток: 24 кружочка от 0 до 24 часов в
местном времени разработчика, красные — дорогие часы, зелёные — дешёвые (в
выходные и праздники вся полоска зелёная), текущий час крупнее и под указателем
«сейчас». Жёлтых кружочков на шкале нет: жёлтой мигает только лампа, предупреждая о
приближении дорогого времени. Под каждым кружочком постоянно видна подпись его часа,
а часы, открывающие зелёный диапазон (после дорогого окна или с начала суток),
выделены жирнее — по ним читается структура дня: для московских окон это 00, 07 и 13.
Подписи часов есть в панелях обоих индикаторов — и в шапке, и под полем ввода.
Сама лампа в шапке подписей не имеет ни в каком варианте; текст рядом с ней — системный
заголовок сессии, его рисует DSH и твик его не трогает.
Подпись под шкалой в покое показывает текущий час, при наведении — час под курсором,
формат один и тот же, поэтому ничего не появляется и не пропадает. Ниже — отсчёт до смены тарифа,
правило тарифов отдельными строками (будни и часы, праздники, текущий часовой пояс)
и строка «Смена тарифа» с адресом: например «пн 09:00 (через 60 ч)» — в пятницу
вечером следующее дорогое окно действительно в понедельник, потому что выходные
целиком дешёвые. Наведение на шкалу подсвечивает час под курсором и его статус: зона
наведения — вся полоска, поэтому на переходах между кружочками шкала не мигает.
В шапке лампа всегда без текста: рядом стоит системный заголовок сессии, который
рисует сам DSH, а не плагин.
Настройки твика — на странице пакета рядом с галочкой, свои у каждого браузера
(лежат в localStorage, ключ dsh.ui-light.settings.v1, хост-половине они не нужны):
| Настройка | Значения |
|---|---|
| Нижний индикатор | только индикатор · индикатор и время, без «ещё» и адреса (по умолчанию) · индикатор и оставшееся время (с адресом) · индикатор, время и тариф |
Умолчания одинаковы для всех: твик включён, нижний индикатор — «Индикатор и время», предупреждение — за 10 минут до пика. Личные правки в браузере перекрывают умолчания только у того, кто их сделал. | Предупреждение | за 10 (по умолчанию), 20 или 30 минут до пика |
Масштаб
zoom ставится корню приложения (#root). Размеры корня не подменяются: движок сам
отдаёт содержимому логическую ширину «ширина / масштаб», и интерфейс заполняет окно.
transform: scale не подходит — с ним расходится попадание курсора в элементы.
Настройку «Font size» в Settings плагин не трогает: она меняет только текст переписки.
Попапы
Меню и подсказки приложение монтирует порталами в body, мимо корня. Общий zoom до них
не достаёт, поэтому каждый слой получает свой, а координаты плагин заворачивает в формулу:
/* приложение пишет --dsh-ui-zoom-src-left: 270px */
left: calc(var(--dsh-ui-zoom-src-left) / var(--dsh-ui-zoom));
Делит браузер, а не JS. --dsh-ui-zoom лежит на корне документа, поэтому смена масштаба
пересчитывает все попапы разом. JS-деление давало дрейф: на нецелом масштабе частное —
периодическая дробь, браузер округляет inline-длину, и меню уезжало в разные места экрана.
offsetWidth и offsetHeight попапов плагин отдаёт приложению в вьюпортных пикселях:
по ним оно выравнивает меню по кнопке и подгоняет его под экран. При смене масштаба
плагин шлёт resize — якорь переехал.
Меню по правой кнопке
Системное меню рисует Electron, и zoom до него не достаёт. Плагин перехватывает правый
клик и показывает своё: в поле ввода — Undo, Redo, Cut, Copy, Paste, Select All,
при выделении вне поля — только Copy. Вставка идёт через Clipboard API.
Своё меню — обычный слой в body, поэтому масштаб ему отдаёт твик zoom через мост
(пока твик выключен, мост молчит: масштабировать нечего). Фон и текст — токены темы.
Подписи берутся через translate(): штатный переводчик отдаёт сам ключ, если словарь
не успел подключиться, и в меню появлялось menu.copy.
Полоски изменения ширины
Приложение прибавляет к ширине панели сдвиг e.clientX - origin, а ширина — в логических
пикселях. Курсор визуальный, поэтому плагин отдаёт событиям на полоске логический
clientX: иначе на 150% панель проходит в полтора раза больше курсора.
Позицию полоска получает как left: viewport - rightbar. Под zoom эта арифметика
расходится с сеткой — при 125% полоска стояла на 1078 px, а граница панели на 1202 px.
Поэтому плагин ставит полоску по границе колонки: offsetLeft и offsetWidth.
Рамка интерфейса
AppFrame измеряет себя через getBoundingClientRect() и из этой ширины считает колонки.
Под zoom движок отдаёт визуальную ширину, поэтому плагин возвращает логическую. Ниже
1024 логических пикселей не опускает: по этому порогу приложение сворачивает сайдбар
(SIDEBAR_AUTO_COLLAPSE). Подмена живёт на уровне пакета и идемпотентна, поэтому
повторное включение твика не делит замер дважды.
Ограничения
- Меню
ApplicationиEditрисует Windows — плагин до них не достаёт. - Чем крупнее масштаб, тем раньше сворачивается сайдбар:
zoomуменьшает логическую ширину, а порог у приложения — 1024 логических пикселя. При окне 1180 px панель сворачивается примерно со 120%. - Ширина правой панели хранится в пикселях, поэтому с ростом масштаба занимает большую долю окна. Меняется перетаскиванием полоски.
- Вставка в своём меню зависит от доступа к буферу обмена.
- Длинное меню ограничивается по высоте в
vh, аvhзум не масштабирует. - На не-loopback Web служба конфигурации недоступна (
status: 'unavailable'): там галочки заблокированы, а твики работают на дефолтах. В Desktop и на loopback всё в порядке. @deepseek-ai/schemasteryобъявлен вpeerDependencies: пакет берётся из установки DSH, отдельная установка не нужна. Правкаindex.jsподхватывается после перезапуска приложения — HMR по умолчанию следит только за файлами профиля (root: []).
Проверка
node --check client.js
node --check index.js
node test\check-client.cjs
node test\check-whisper.cjs
node test\check-host.cjs
node test\check-traffic-light.cjs
test/check-client.cjs гоняет настоящий client.js в песочнице с заглушками DOM,
React, ctx и configForms: 56 проверок — флаги включают и выключают твики,
твик без клиентского кода (whisper) включается и выключается галочкой, повторное
включение не копит обработчики, дефолты без службы конфигурации, форма пишет
значения (операция set + ревизия), шаг и границы масштаба, хранилище, команды и их
привязки, плашка, Ctrl + колесо, попапы (перехват координат, calc(), сигнал
resize, отсутствие дрейфа), полоски, рамка и меню по правой кнопке.
test/check-host.cjs проверяет хост-половину без установки DSH: index.js грузится
с заглушкой @deepseek-ai/schemastery, флаг поднимает настоящий
tweaks/whisper-host.js, смена модели перезапускает его, выгрузка снимает, а
пропажа файла хост-части не роняет плагин. Там же — разовая настройка
распознавания: первое включение зовёт configure ровно один раз и сохраняет
признак в своё поле схемы, повторный запуск с готовым признаком выбор не трогает,
падение configure и отсутствие службы распознавания твик не роняют.
test/check-whisper.cjs — контракт самой хост-части.
test/zoom-stand.html прогоняется в headless Edge в реальном времени: --dump-dom
отдаёт страницу до конца её асинхронной работы, а --virtual-time-budget ломает
порядок ResizeObserver и даёт ложный сбой на перетаскивании полоски. Стенд прогоняет
связку «якорь — портал» на всех масштабах 0.5…2.0 и четырёх раскладках, отдельно
проверяет полоску панели, перетаскивание и меню под курсором.