skillok meeting
169ce64e1705fd64e48ee357a816bbdb90bab01d
Перейти к файлу
Код
meeting src-taurisrclib.rs
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 г.
lib.rs
//! Skillok Meet — ядро приложения. Интерфейс зовёт команды отсюда через
//! `invoke`; всё, что трогает диск, сеть, звук и ключи, живёт в Rust.

pub mod analysis;
pub mod controller;
pub mod meetings;
pub mod openrouter;
pub mod recorder;
pub mod settings;
pub mod transcript;

mod commands;
mod shell;

use std::sync::Arc;

use tauri::Manager;

use controller::{Controller, Deps, OpenRouterAi, SystemSources};
use settings::Settings;

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_notification::init())
        .plugin(tauri_plugin_autostart::init(
            tauri_plugin_autostart::MacosLauncher::LaunchAgent,
            Some(vec!["--hidden"]),
        ))
        .setup(|app| {
            let handle = app.handle().clone();
            let config_dir = app.path().app_config_dir()?;
            let data_dir = app.path().app_data_dir()?;
            let settings_path = settings::settings_path(&config_dir);
            let store = Arc::new(meetings::Store::new(data_dir.join("meetings")));
            // Запись, оборванная выключением компьютера, — «прервана», а не «идёт».
            let _ = store.recover_interrupted();
            let secrets: Arc<dyn settings::Secrets> = Arc::new(settings::Keyring);

            let analyze_store = store.clone();
            let controller = Controller::new(Deps {
                store: store.clone(),
                settings_path: settings_path.clone(),
                secrets: secrets.clone(),
                outlet: Arc::new(shell::TauriOutlet { app: handle.clone() }),
                sources: Arc::new(SystemSources::from_env()),
                rt: tauri::async_runtime::handle().inner().clone(),
                make_ai: Arc::new(|key, settings| Arc::new(OpenRouterAi::new(key, settings))),
                analyze: Arc::new(move |id, settings, key| {
                    let store = analyze_store.clone();
                    Box::pin(async move { commands::analyze(&store, &settings, &key, &id).await.map(|_| ()) })
                }),
                recorder: recorder::Config::default(),
            });
            controller.spawn_watch();

            app.manage(commands::AppState {
                settings_path: settings_path.clone(),
                store,
                secrets,
                controller,
            });
            shell::build_tray(&handle)?;
            let launch = Settings::load(&settings_path)
                .map(|s| s.launch_at_login)
                .unwrap_or(true);
            shell::apply_autostart(&handle, launch);

            // Запуск вместе с Windows — сразу в трей, без окна.
            if std::env::args().any(|a| a == "--hidden") {
                if let Some(w) = app.get_webview_window("main") {
                    let _ = w.hide();
                }
            }
            Ok(())
        })
        .on_window_event(|window, event| {
            if let tauri::WindowEvent::CloseRequested { api, .. } = event {
                api.prevent_close();
                let _ = window.hide();
            }
        })
        .invoke_handler(tauri::generate_handler![
            commands::get_settings,
            commands::save_settings,
            commands::set_openrouter_key,
            commands::check_openrouter_key,
            commands::delete_openrouter_key,
            commands::list_meetings,
            commands::get_meeting,
            commands::import_meeting,
            commands::analyze_meeting,
            commands::recorder_status,
            commands::recorder_consent,
            commands::recorder_start,
            commands::recorder_pause,
            commands::recorder_stop,
        ])
        .run(tauri::generate_context!())
        .expect("Skillok Meet не запустился");
}