40 lines
1.9 KiB
TypeScript
40 lines
1.9 KiB
TypeScript
import { listen, emit } from '@tauri-apps/api/event';
|
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import type { LiveState } from '../types';
|
|
import { getCachedLiveState } from '../lib/liveBus';
|
|
import { useAppStore } from '../store/useAppStore';
|
|
import { isTauriRuntime } from '../lib/tauri';
|
|
|
|
export function Overlay() {
|
|
const settings = useAppStore((s) => s.settings);
|
|
const [state, setState] = useState<LiveState | undefined>(() => getCachedLiveState());
|
|
useEffect(() => {
|
|
if (!isTauriRuntime()) return;
|
|
let unlisten: (() => void) | undefined;
|
|
listen<LiveState>('meetvault:live-state', (event) => setState(event.payload)).then((fn) => { unlisten = fn; });
|
|
return () => unlisten?.();
|
|
}, []);
|
|
|
|
const visible = useMemo(() => state?.transcript.slice(-settings.overlayLines) ?? [], [state, settings.overlayLines]);
|
|
|
|
async function rename(speakerId: string) {
|
|
const current = state?.speakerNames[speakerId] || speakerId;
|
|
const next = window.prompt('Rename speaker', current);
|
|
if (!next) return;
|
|
if (isTauriRuntime()) await emit('meetvault:rename-speaker', { meetingId: state?.meetingId, speakerId, name: next });
|
|
}
|
|
|
|
return (
|
|
<div className="overlay-root" style={{ opacity: settings.overlayOpacity / 100, fontSize: settings.overlayFontSize }}>
|
|
<div className="overlay-header"><span>MeetVault live captions</span>{isTauriRuntime() && <button onClick={() => getCurrentWindow().hide()}>Hide</button>}</div>
|
|
{visible.length === 0 ? <div className="overlay-empty">Waiting for speech…</div> : visible.map((segment) => (
|
|
<div className="overlay-line" key={segment.id}>
|
|
<button onClick={() => rename(segment.speakerId)}>{state?.speakerNames[segment.speakerId] || segment.speakerId}</button>
|
|
<span>{segment.text}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|