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 г.
scope.ts
// Область экрана: подписки и таймеры, которые снимаются при уходе с него.
export class Scope {
private disposers: (() => void)[] = [];
private closed = false;
/** Подписка на событие ядра: `on(events.status(cb))`. */
on(unsubscribe: Promise<() => void>): void {
unsubscribe.then((off) => (this.closed ? off() : this.disposers.push(off)));
}
every(ms: number, fn: () => void): void {
const id = window.setInterval(fn, ms);
this.disposers.push(() => clearInterval(id));
}
/** Вложенная область: снимается вместе с этой или раньше — сама. */
child(): Scope {
const c = new Scope();
this.disposers.push(() => c.dispose());
return c;
}
get alive(): boolean {
return !this.closed;
}
dispose(): void {
this.closed = true;
this.disposers.splice(0).forEach((off) => off());
}
}