skillok meeting
43a49bdad7084369e41933f51c5fa3e45dec2040
Перейти к файлу
Код
meeting srcrecording.ts
anc Интерфейс как у MacWhisper в стиле Skillok, только светлая тема - Слева библиотека записей: «Записать», «Добавить аудиофайл», список, «Настройки» внизу; пока идёт запись — метка с таймером вместо кнопки. - Экран записи: сверху название и «Копировать», расшифровка по репликам (время — кто — текст), под ней полоса «Дальше» с Конспектом, Задачами, Ревью и своим запросом. Шаги-«лесенка» убраны. - Ход расшифровки — крупный процент и полоса. - Тёмная тема убрана, окно Tauri всегда светлое. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 43a49bd 30 сентября 2026 г.
recording.ts
// Запись: панель с таймером, уровнями и кнопками — одна на главной и на
// экране записи. Метка «идёт запись» с любого экрана — в боковой панели (side.ts).

import { api, elapsed, events } from "./api";
import { esc } from "./dom";
import type { Scope } from "./scope";
import { changed, copy, refreshRecording, state, toast } from "./state";

export function recordingPanel(warning: string): string {
  const r = state.recording;
  if (!r) return "";
  const meter = (label: string, key: string) => `
    <div class="meter"><span class="meter-label small">${label}</span>
      <span class="meter-bar"><span class="meter-fill" data-level="${key}"></span></span></div>`;
  return `
    <div class="rec" data-rec>
      <p class="rec-state"><span class="dot dot-live${r.paused ? " is-paused" : ""}"></span>${
        r.paused ? "Запись на паузе" : r.system ? "Идёт запись: собеседники и вы" : "Идёт запись: только ваш микрофон"
      }</p>
      ${r.system_error ? `<p class="rec-warn small">Голоса собеседников не пишутся: ${esc(r.system_error)}</p>` : ""}
      <p class="rec-timer mono-num" data-timer>${elapsed(r.started_at)}</p>
      <div class="meters">${meter("Вы", "me")}${r.system ? meter("Собеседники", "others") : ""}</div>
      <div class="actions">
        <button class="ui-btn" data-rec-stop>Остановить</button>
        <button class="link-btn" data-rec-pause>${r.paused ? "Продолжить" : "Пауза"}</button>
        <button class="link-btn" data-rec-warning>Скопировать предупреждение для чата</button>
      </div>
      <template data-warning>${esc(warning)}</template>
    </div>`;
}

/** Кнопки панели и живые уровни звука. `onStop` — после остановки. */
export function bindRecording(root: HTMLElement, scope: Scope, warning: string, onStop: () => void): void {
  root.querySelector<HTMLButtonElement>("[data-rec-stop]")?.addEventListener("click", async (e) => {
    const b = e.currentTarget as HTMLButtonElement;
    b.disabled = true;
    b.textContent = "Останавливаю…";
    try {
      await api.recorderStop();
      await refreshRecording();
      onStop();
    } catch (err) {
      b.disabled = false;
      toast(err);
    }
  });
  root.querySelector("[data-rec-pause]")?.addEventListener("click", async () => {
    if (!state.recording) return;
    try {
      await api.recorderPause(!state.recording.paused);
      state.recording.paused = !state.recording.paused;
      changed();
    } catch (err) {
      toast(err);
    }
  });
  root.querySelector("[data-rec-warning]")?.addEventListener("click", () => copy(warning, "Предупреждение скопировано — вставьте его в чат встречи"));
  scope.on(
    events.recorder(({ event }) => {
      if (event.kind === "level") {
        const key = event.data.speaker.kind === "me" ? "me" : "others";
        const fill = root.querySelector<HTMLElement>(`[data-level="${key}"]`);
        if (fill) fill.style.width = `${Math.max(0, Math.min(100, ((event.data.dbfs + 60) / 55) * 100))}%`;
      } else {
        toast(`Дорожка «${event.data.speaker.kind === "me" ? "вы" : "собеседники"}» перестала писаться: ${event.data.error}`);
      }
    }),
  );
  scope.every(1000, () => {
    const t = root.querySelector("[data-timer]");
    if (t && state.recording) t.textContent = elapsed(state.recording.started_at);
  });
}