52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
'use client';
|
|
|
|
import { useSyncExternalStore } from 'react';
|
|
import { useSettingsStore } from '@/lib/store/settings';
|
|
import { ASR_PROVIDERS } from '@/lib/audio/constants';
|
|
import type { BuiltInASRProviderId } from '@/lib/audio/types';
|
|
|
|
// Web Speech API support is constant per environment, so subscribing is a no-op.
|
|
const subscribe = () => () => {};
|
|
const getBrowserSpeechSupported = () =>
|
|
!!(window.SpeechRecognition || window.webkitSpeechRecognition);
|
|
|
|
/**
|
|
* Single source of truth for "can the user use ASR right now".
|
|
*
|
|
* Combines the three things that gate speech input:
|
|
* - the global on/off toggle (`asrEnabled`),
|
|
* - whether the selected provider is actually usable (keyless, has a client
|
|
* key, or is server-managed — mirrors the `cfgOk` check in media-popover),
|
|
* - and, for `browser-native`, whether the browser supports the Web Speech API.
|
|
*
|
|
* Consumed by `SpeechButton` so every call site is gated uniformly instead of
|
|
* each one having to remember to wire the toggle through a `disabled` prop.
|
|
*/
|
|
export function useASRAvailable(): boolean {
|
|
const asrEnabled = useSettingsStore((s) => s.asrEnabled);
|
|
const asrProviderId = useSettingsStore((s) => s.asrProviderId);
|
|
const providerConfig = useSettingsStore((s) => s.asrProvidersConfig[s.asrProviderId]);
|
|
|
|
// SSR-safe Web Speech support check: the server snapshot assumes supported so
|
|
// the button is never falsely disabled before hydration.
|
|
const browserSpeechSupported = useSyncExternalStore(
|
|
subscribe,
|
|
getBrowserSpeechSupported,
|
|
() => true,
|
|
);
|
|
|
|
const builtIn = ASR_PROVIDERS[asrProviderId as BuiltInASRProviderId];
|
|
const requiresKey = builtIn ? builtIn.requiresApiKey : (providerConfig?.requiresApiKey ?? true);
|
|
// Trim to match what the recorder actually sends — a whitespace-only key is
|
|
// dropped at request time, so it must not count as configured here.
|
|
const keyOk =
|
|
!requiresKey || !!providerConfig?.apiKey?.trim() || !!providerConfig?.isServerConfigured;
|
|
// Custom providers are unusable until at least one model is configured
|
|
// (mirrors the media-popover listing rule that hides model-less customs).
|
|
const modelOk = !!builtIn || (providerConfig?.customModels?.length ?? 0) > 0;
|
|
const browserOk = asrProviderId !== 'browser-native' || browserSpeechSupported;
|
|
|
|
if (typeof window !== 'undefined' && window.__MAKELORE_COURSE_CONTEXT__) return true;
|
|
return asrEnabled && keyOk && modelOk && browserOk;
|
|
}
|