mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-18 14:20:42 -07:00
Stops the "stuck pill" failure where pressing the chord with missing STT/LLM models triggers a recording that has nowhere to land. The hotkey now stays disarmed until every gate (models downloaded, Input Monitoring + Accessibility granted) is green; the empty-state checklist in CapturesTab surfaces each unmet gate with a one-click action and auto-arms the chord once everything turns green. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { useAccessibilityPermission } from '@/components/AccessibilityGate/AccessibilityGate';
|
|
import { useInputMonitoringPermission } from '@/components/InputMonitoringGate/InputMonitoringGate';
|
|
import { apiClient } from '@/lib/api/client';
|
|
import type { ModelReadiness } from '@/lib/api/types';
|
|
import { usePlatform } from '@/platform/PlatformContext';
|
|
|
|
const READINESS_POLL_INTERVAL_MS = 5_000;
|
|
|
|
export type ReadinessGate = 'stt' | 'llm' | 'input_monitoring' | 'accessibility';
|
|
|
|
export interface DictationReadiness {
|
|
isLoading: boolean;
|
|
allReady: boolean;
|
|
/** Subset of gates that are NOT yet satisfied — what the checklist renders. */
|
|
missing: ReadinessGate[];
|
|
stt: ModelReadiness | undefined;
|
|
llm: ModelReadiness | undefined;
|
|
inputMonitoring: boolean;
|
|
accessibility: boolean;
|
|
refetch: () => void;
|
|
openInputMonitoringSettings: () => Promise<void>;
|
|
openAccessibilitySettings: () => Promise<void>;
|
|
recheckInputMonitoring: () => Promise<boolean>;
|
|
recheckAccessibility: () => Promise<boolean>;
|
|
}
|
|
|
|
/**
|
|
* Single source of truth for "can the user trigger dictation right now?"
|
|
*
|
|
* Combines four gates into one struct so the chord-sync hook can refuse to
|
|
* arm the global hotkey unless every gate is green — the "stuck pill" we
|
|
* used to get on missing models is solved by never letting the chord fire
|
|
* in the first place.
|
|
*
|
|
* Gates:
|
|
* - stt / llm: backend ``/capture/readiness`` (polled, since downloads
|
|
* finish out-of-band — e.g. user kicks off a download in another tab and
|
|
* expects the toggle to auto-unlock when it lands)
|
|
* - input_monitoring / accessibility: macOS TCC checks via Tauri commands
|
|
* (rechecked on window focus by the underlying hooks)
|
|
*
|
|
* Hotkey-enabled is the user's intent toggle and is intentionally *not*
|
|
* a gate here — that's `useChordSync`'s concern.
|
|
*/
|
|
export function useDictationReadiness(): DictationReadiness {
|
|
const platform = usePlatform();
|
|
const isTauri = platform.metadata.isTauri;
|
|
|
|
const {
|
|
needsPermission: inputMonNeeds,
|
|
recheck: recheckInputMon,
|
|
openSettings: openInputMon,
|
|
} = useInputMonitoringPermission();
|
|
const {
|
|
needsPermission: a11yNeeds,
|
|
recheck: recheckA11y,
|
|
openSettings: openA11y,
|
|
} = useAccessibilityPermission();
|
|
|
|
const { data, isLoading, refetch } = useQuery({
|
|
queryKey: ['capture-readiness'],
|
|
queryFn: () => apiClient.getCaptureReadiness(),
|
|
refetchInterval: READINESS_POLL_INTERVAL_MS,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
|
|
// On the web build there's no TCC layer — treat both as granted so the
|
|
// checklist doesn't block users who can't even open System Settings.
|
|
const inputMonitoring = isTauri ? !inputMonNeeds : true;
|
|
const accessibility = isTauri ? !a11yNeeds : true;
|
|
const sttReady = data?.stt.ready ?? false;
|
|
const llmReady = data?.llm.ready ?? false;
|
|
|
|
const missing: ReadinessGate[] = [];
|
|
if (!sttReady) missing.push('stt');
|
|
if (!llmReady) missing.push('llm');
|
|
if (!inputMonitoring) missing.push('input_monitoring');
|
|
if (!accessibility) missing.push('accessibility');
|
|
|
|
return {
|
|
isLoading,
|
|
allReady: missing.length === 0,
|
|
missing,
|
|
stt: data?.stt,
|
|
llm: data?.llm,
|
|
inputMonitoring,
|
|
accessibility,
|
|
refetch: () => {
|
|
refetch();
|
|
},
|
|
openInputMonitoringSettings: openInputMon,
|
|
openAccessibilitySettings: openA11y,
|
|
recheckInputMonitoring: recheckInputMon,
|
|
recheckAccessibility: recheckA11y,
|
|
};
|
|
}
|