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 г.
main.ts
// Маршрутизация по хешу: #/ (главная), #/meeting//<вкладка>[/<реплика>], #/settings.
import { renderHome } from "./views/home";
import { renderMeeting, type Tab } from "./views/meeting";
import { renderSettings } from "./views/settings";
import { errorText } from "./api";
import { esc } from "./dom";
import { Scope } from "./scope";
const app = document.getElementById("app")!;
let scope = new Scope();
async function route(): Promise {
scope.dispose();
scope = new Scope();
const [section, id, tab, target] = location.hash.replace(/^#\/?/, "").split("/");
for (const a of document.querySelectorAll("[data-nav]")) {
const current = a.dataset.nav === (section === "settings" ? "settings" : "home");
if (current) a.setAttribute("aria-current", "page");
else a.removeAttribute("aria-current");
}
try {
if (section === "meeting" && id) {
await renderMeeting(app, scope, decodeURIComponent(id), (tab as Tab) || "transcript", target ? Number(target) : null);
} else if (section === "settings") {
await renderSettings(app);
} else {
await renderHome(app, scope);
}
} catch (e) {
app.innerHTML = `${esc(errorText(e))}`;
}
}
window.addEventListener("hashchange", route);
route();