anc
Этап 1: запись встречи в Zoom, живая расшифровка и разбор
- crates/audio: захват звука процесса Zoom и микрофона (WASAPI, process
loopback), формат 16 кГц моно, нарезка на куски по паузам с отсевом
тишины, распознавание начала и конца встречи по звуковой сессии Zoom.
- Ядро: запись двух дорожек → Whisper через OpenRouter → расшифровка,
живой конспект раз в три минуты, по окончании — разбор сам; звук
удаляется после разбора, если не включено хранение.
- Контроллер: слежу за Zoom → прошу предупредить участников → пишу;
запись вручную, пауза, стоп; прерванная запись помечается при запуске.
- Трей, уведомления, крестик прячет окно, запуск вместе с Windows.
- Интерфейс: главный экран с карточкой состояния (задача #1), живая
расшифровка и конспект на экране встречи, новые настройки.
- reqwest на системном TLS: под Windows без C-кода ring.
- 67 тестов; код под Windows проверен компиляцией и clippy с Mac,
на Windows не запускался.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
169ce64
30 сентября 2026 г.
dom.ts
// Маленькие помощники вёрстки: экранирование и сборка разметки строкой.
export function esc(value: unknown): string {
return String(value ?? "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
export function list(items: string[]): string {
return `${items.map((i) => `${esc(i)}`).join("")}`;
}
export function $(root: ParentNode, selector: string): HTMLElement {
const el = root.querySelector(selector);
if (!el) throw new Error(`нет элемента ${selector}`);
return el;
}
/** Кнопка в ожидании: недоступна и с другой подписью, пока идёт `work`. */
export async function busy(button: HTMLButtonElement, label: string, work: () => Promise): Promise {
const before = button.textContent;
button.disabled = true;
button.textContent = label;
try {
return await work();
} finally {
button.disabled = false;
button.textContent = before;
}
}
/** Русское число: plural(3, "задача", "задачи", "задач") → «задачи». */
export function plural(n: number, one: string, few: string, many: string): string {
const d = n % 10, h = n % 100;
if (d === 1 && h !== 11) return one;
if (d >= 2 && d <= 4 && (h < 12 || h > 14)) return few;
return many;
}